OpenJDK / amber / amber
changeset 56790:892a23eb13ce patterns-deconstruction
Merging patterns and records-and-sealed branches into a new branch patterns-deconstruction.
line wrap: on
line diff
--- a/.hgignore Fri Jun 14 08:37:37 2019 +0200 +++ b/.hgignore Fri Jun 14 11:12:54 2019 +0200 @@ -11,6 +11,6 @@ test/nashorn/script/external test/nashorn/lib NashornProfile.txt -.*/JTreport/.* -.*/JTwork/.* +JTreport/ +JTwork/ .*/.git/.*
--- a/make/autoconf/spec.gmk.in Fri Jun 14 08:37:37 2019 +0200 +++ b/make/autoconf/spec.gmk.in Fri Jun 14 11:12:54 2019 +0200 @@ -649,6 +649,7 @@ --add-exports java.base/sun.reflect.annotation=jdk.compiler.interim \ --add-exports java.base/jdk.internal.jmod=jdk.compiler.interim \ --add-exports java.base/jdk.internal.misc=jdk.compiler.interim \ + --add-exports java.base/sun.invoke.util=jdk.compiler.interim \ # INTERIM_LANGTOOLS_MODULES_COMMA := $(strip $(subst $(SPACE),$(COMMA),$(strip \ $(INTERIM_LANGTOOLS_MODULES))))
--- a/make/hotspot/symbols/symbols-unix Fri Jun 14 08:37:37 2019 +0200 +++ b/make/hotspot/symbols/symbols-unix Fri Jun 14 11:12:54 2019 +0200 @@ -119,9 +119,12 @@ JVM_GetNanoTimeAdjustment JVM_GetNestHost JVM_GetNestMembers +JVM_GetPermittedSubtypes JVM_GetPrimitiveArrayElement JVM_GetProperties JVM_GetProtectionDomain +JVM_GetRecordParameters +JVM_GetRecordParametersCount JVM_GetSimpleBinaryName JVM_GetStackAccessControlContext JVM_GetSystemPackage
--- a/src/hotspot/share/classfile/classFileParser.cpp Fri Jun 14 08:37:37 2019 +0200 +++ b/src/hotspot/share/classfile/classFileParser.cpp Fri Jun 14 11:12:54 2019 +0200 @@ -1749,6 +1749,7 @@ _fields->at_put(i++, fa[j]); } assert(_fields->length() == i, ""); + //tty->print_cr("length of the _fields array %d for class %s", i, _class_name->as_klass_external_name()); } if (_need_verify && length > 1) { @@ -3223,6 +3224,122 @@ return length; } +u2 ClassFileParser::parse_classfile_permitted_subtypes_attribute(const ClassFileStream* const cfs, + const u1* const permitted_subtypes_attribute_start, + TRAPS) { + const u1* const current_mark = cfs->current(); + u2 length = 0; + if (permitted_subtypes_attribute_start != NULL) { + cfs->set_current(permitted_subtypes_attribute_start); + cfs->guarantee_more(2, CHECK_0); // length + length = cfs->get_u2_fast(); + } + const int size = length; + Array<u2>* const permitted_subtypes = MetadataFactory::new_array<u2>(_loader_data, size, CHECK_0); + _permitted_subtypes = permitted_subtypes; + + int index = 0; + cfs->guarantee_more(2 * length, CHECK_0); + for (int n = 0; n < length; n++) { + const u2 class_info_index = cfs->get_u2_fast(); + check_property( + valid_klass_reference_at(class_info_index), + "Permitted subtype class_info_index %u has bad constant type in class file %s", + class_info_index, CHECK_0); + permitted_subtypes->at_put(index++, class_info_index); + } + assert(index == size, "wrong size"); + + // Restore buffer's current position. + cfs->set_current(current_mark); + + return length; +} + +void ClassFileParser::parse_classfile_record_attribute(const ClassFileStream* const cfs, + const u1* const record_attribute_start, + ConstantPool* cp, + u2* const record_params_count_ptr, + TRAPS) { + assert(NULL == _record_params, "invariant"); + + const u1* const current_mark = cfs->current(); + u2 num_of_params = 0; + if (record_attribute_start != NULL) { + cfs->set_current(record_attribute_start); + cfs->guarantee_more(2, CHECK); // length + num_of_params = cfs->get_u2_fast(); + // DEBUG + // tty->print_cr("this record has %d parameters", num_of_params); + } + + *record_params_count_ptr = num_of_params; + + ResourceMark rm(THREAD); + u2* const record_params_array = NEW_RESOURCE_ARRAY_IN_THREAD(THREAD, + u2, + num_of_params * (RecordParamInfo::param_slots + 1)); + for (int n = 0; n < num_of_params; n++) { + cfs->guarantee_more(RecordParamInfo::param_slots, CHECK); + + const u2 name_index = cfs->get_u2_fast(); + check_property(valid_symbol_at(name_index), + "Invalid constant pool index %u for record parameter name in class file %s", + name_index, CHECK); + const Symbol* const name = cp->symbol_at(name_index); + verify_legal_field_name(name, CHECK); + // DEBUG + // tty->print_cr("name read %s", name->as_klass_external_name()); + + AccessFlags access_flags; + const jint flags = cfs->get_u2_fast() & JVM_RECOGNIZED_FIELD_MODIFIERS; + verify_legal_field_modifiers(flags, false, CHECK); + access_flags.set_flags(flags); + + const u2 descriptor_index = cfs->get_u2_fast(); + check_property(valid_symbol_at(descriptor_index), + "Invalid constant pool index %u for record parameter descriptor in class file %s", + descriptor_index, CHECK); + const Symbol* const descriptor = cp->symbol_at(descriptor_index); + verify_legal_field_signature(name, descriptor, CHECK); + // DEBUG + // tty->print_cr("descriptor read %s", descriptor->as_klass_external_name()); + + const u2 signature_index = cfs->get_u2_fast(); + check_property(valid_symbol_at(signature_index), + "Invalid constant pool index %u for record parameter signature in class file %s", + signature_index, CHECK); + const Symbol* const sig = cp->symbol_at(signature_index); + // DEBUG + // tty->print_cr("signature read %s", sig->as_klass_external_name()); + + RecordParamInfo* const record_param_info = RecordParamInfo::from_record_params_array(record_params_array, n); + record_param_info->initialize( + access_flags.as_short(), + name_index, + descriptor_index, + signature_index); + } + + assert(NULL == _record_params, "invariant"); + + _record_params = MetadataFactory::new_array<u2>(_loader_data, + num_of_params * RecordParamInfo::param_slots, + CHECK); + { + int i = 0; + for (; i < num_of_params * RecordParamInfo::param_slots; i++) { + _record_params->at_put(i, record_params_array[i]); + } + assert(_record_params->length() == i, ""); + // DEBUG + // tty->print_cr("length of the _record_params array %d for class %s", i, _class_name->as_klass_external_name()); + } + + // Restore buffer's current position. + cfs->set_current(current_mark); +} + void ClassFileParser::parse_classfile_synthetic_attribute(TRAPS) { set_class_synthetic_flag(true); } @@ -3332,12 +3449,18 @@ _inner_classes = Universe::the_empty_short_array(); // Set nest members attribute to default sentinel _nest_members = Universe::the_empty_short_array(); + // Set _permitted_subtypes attribute to default sentinel + _permitted_subtypes = Universe::the_empty_short_array(); + // Set record params to default sentinel + _record_params = Universe::the_empty_short_array(); cfs->guarantee_more(2, CHECK); // attributes_count u2 attributes_count = cfs->get_u2_fast(); bool parsed_sourcefile_attribute = false; bool parsed_innerclasses_attribute = false; bool parsed_nest_members_attribute = false; + bool parsed_permitted_subtypes_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 +3480,10 @@ 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; + const u1* permitted_subtypes_attribute_start = NULL; + u4 permitted_subtypes_attribute_length = 0; // Iterate over attributes while (attributes_count--) { @@ -3539,6 +3666,25 @@ "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_permitted_subtypes()) { + // Check for PermittedSubtypes tag + if (parsed_permitted_subtypes_attribute) { + classfile_parse_error("Multiple PermittedSubtypes attributes in class file %s", CHECK); + } else { + parsed_permitted_subtypes_attribute = true; + } + permitted_subtypes_attribute_start = cfs->current(); + permitted_subtypes_attribute_length = attribute_length; + cfs->skip_u1(permitted_subtypes_attribute_length, CHECK); + } else if (tag == vmSymbols::tag_record()) { + if (parsed_record_attribute) { + classfile_parse_error("Multiple Record attributes in class file %s", CHECK); + } else { + parsed_record_attribute = true; + } + record_attribute_start = cfs->current(); + record_attribute_length = attribute_length; + cfs->skip_u1(record_attribute_length, CHECK); } else { // Unknown attribute cfs->skip_u1(attribute_length, CHECK); @@ -3590,6 +3736,27 @@ } } + if (parsed_record_attribute) { + parse_classfile_record_attribute( + cfs, + record_attribute_start, + cp, + &_record_params_count, + CHECK); + } + + if (parsed_permitted_subtypes_attribute) { + const u2 num_of_subtypes = parse_classfile_permitted_subtypes_attribute( + cfs, + permitted_subtypes_attribute_start, + CHECK); + if (_need_verify) { + guarantee_property( + permitted_subtypes_attribute_length == sizeof(num_of_subtypes) + sizeof(u2) * num_of_subtypes, + "Wrong PermittedSubtypes attribute 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 +3811,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, + int record_params_count, TRAPS) { assert(this_klass != NULL, "invariant"); _cp->set_pool_holder(this_klass); @@ -3656,6 +3824,8 @@ 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_params(_record_params, record_params_count); + this_klass->set_permitted_subtypes(_permitted_subtypes); // 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 @@ -4727,12 +4897,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) || - (is_interface && !is_abstract) || - (is_interface && major_gte_15 && (is_super || is_enum)) || - (!is_interface && major_gte_15 && is_annotation)) { + const bool major_gte_1_5 = _major_version >= JAVA_1_5_VERSION; + const bool major_gte_12 = _major_version >= JAVA_12_VERSION; + + if ((is_abstract && is_final && !major_gte_12) || + (is_interface && !is_abstract && !major_gte_12) || + (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, @@ -5495,7 +5666,7 @@ // this transfers ownership of a lot of arrays from // the parser onto the InstanceKlass* - apply_parsed_class_metadata(ik, _java_fields_count, CHECK); + apply_parsed_class_metadata(ik, _java_fields_count, _record_params_count, CHECK); // note that is not safe to use the fields in the parser from this point on assert(NULL == _cp, "invariant"); @@ -5505,6 +5676,8 @@ assert(NULL == _nest_members, "invariant"); assert(NULL == _local_interfaces, "invariant"); assert(NULL == _combined_annotations, "invariant"); + assert(NULL == _record_params, "invariant"); + assert(NULL == _permitted_subtypes, "invariant"); if (_has_final_method) { ik->set_has_final_method(); @@ -5684,6 +5857,8 @@ // it's official set_klass(ik); + check_subtyping(CHECK); + debug_only(ik->verify();) } @@ -5786,6 +5961,8 @@ _inner_classes(NULL), _nest_members(NULL), _nest_host(0), + _record_params(NULL), + _permitted_subtypes(NULL), _local_interfaces(NULL), _transitive_interfaces(NULL), _combined_annotations(NULL), @@ -5819,6 +5996,7 @@ _super_class_index(0), _itfs_len(0), _java_fields_count(0), + _record_params_count(0), _need_verify(false), _relax_verify(false), _has_nonstatic_concrete_methods(false), @@ -5892,10 +6070,12 @@ _methods = NULL; _inner_classes = NULL; _nest_members = NULL; + _permitted_subtypes = NULL; _local_interfaces = NULL; _combined_annotations = NULL; _annotations = _type_annotations = NULL; _fields_annotations = _fields_type_annotations = NULL; + _record_params = NULL; } // Destructor to clean up @@ -5923,6 +6103,14 @@ MetadataFactory::free_array<u2>(_loader_data, _nest_members); } + if (_record_params != NULL && _record_params != Universe::the_empty_short_array()) { + MetadataFactory::free_array<u2>(_loader_data, _record_params); + } + + if (_permitted_subtypes != NULL && _permitted_subtypes != Universe::the_empty_short_array()) { + MetadataFactory::free_array<u2>(_loader_data, _permitted_subtypes); + } + // Free interfaces InstanceKlass::deallocate_interfaces(_loader_data, _super_klass, _local_interfaces, _transitive_interfaces); @@ -6255,10 +6443,6 @@ ); return; } - // Make sure super class is not final - if (_super_klass->is_final()) { - THROW_MSG(vmSymbols::java_lang_VerifyError(), "Cannot inherit from final class"); - } } // Compute the transitive list of all unique interfaces implemented by this class @@ -6276,12 +6460,16 @@ _all_mirandas = new GrowableArray<Method*>(20); Handle loader(THREAD, _loader_data->class_loader()); + bool is_sealed = _permitted_subtypes != NULL && + _permitted_subtypes != Universe::the_empty_short_array() && + _permitted_subtypes->length() > 0; klassVtable::compute_vtable_size_and_num_mirandas(&_vtable_size, &_num_miranda_methods, _all_mirandas, _super_klass, _methods, _access_flags, + is_sealed, _major_version, loader, _class_name, @@ -6303,6 +6491,34 @@ } +void ClassFileParser::check_subtyping(TRAPS) { + assert(NULL != _klass, "_klass should have been resolved before calling this method"); + if (_super_klass != NULL) { + if (_super_klass->is_final()) { + THROW_MSG(vmSymbols::java_lang_VerifyError(), "Cannot inherit from final class"); + } else if (_super_klass->is_sealed()) { + bool isPermittedSubtype = _super_klass->has_as_permitted_subtype(_klass, CHECK); + if (!isPermittedSubtype) { + THROW_MSG(vmSymbols::java_lang_VerifyError(), "Cannot inherit from sealed class"); + } + } + } + Array<InstanceKlass*>* local_interfaces = _klass->local_interfaces(); + if (local_interfaces != NULL && local_interfaces != Universe::the_empty_instance_klass_array()) { + for (int i = 0; i < local_interfaces->length(); i++) { + InstanceKlass* intf = local_interfaces->at(i); + if (intf->is_final()) { + THROW_MSG(vmSymbols::java_lang_VerifyError(), "Cannot inherit from final interface"); + } else if (intf->is_sealed()) { + bool isPermittedSubtype = intf->has_as_permitted_subtype(_klass, CHECK); + if (!isPermittedSubtype) { + THROW_MSG(vmSymbols::java_lang_VerifyError(), "Cannot inherit from sealed interface"); + } + } + } + } +} + void ClassFileParser::set_klass(InstanceKlass* klass) { #ifdef ASSERT
--- a/src/hotspot/share/classfile/classFileParser.hpp Fri Jun 14 08:37:37 2019 +0200 +++ b/src/hotspot/share/classfile/classFileParser.hpp Fri Jun 14 11:12:54 2019 +0200 @@ -98,6 +98,8 @@ Array<u2>* _inner_classes; Array<u2>* _nest_members; u2 _nest_host; + Array<u2>* _record_params; + Array<u2>* _permitted_subtypes; Array<InstanceKlass*>* _local_interfaces; Array<InstanceKlass*>* _transitive_interfaces; Annotations* _combined_annotations; @@ -152,6 +154,7 @@ u2 _super_class_index; u2 _itfs_len; u2 _java_fields_count; + u2 _record_params_count; bool _need_verify; bool _relax_verify; @@ -187,7 +190,7 @@ void create_combined_annotations(TRAPS); void apply_parsed_class_attributes(InstanceKlass* k); // update k - void apply_parsed_class_metadata(InstanceKlass* k, int fields_count, TRAPS); + void apply_parsed_class_metadata(InstanceKlass* k, int fields_count, int record_params_count, TRAPS); void clear_class_metadata(); // Constant pool parsing @@ -287,6 +290,16 @@ const u1* const nest_members_attribute_start, TRAPS); + u2 parse_classfile_permitted_subtypes_attribute(const ClassFileStream* const cfs, + const u1* const permitted_subtypes_attribute_start, + TRAPS); + + void parse_classfile_record_attribute(const ClassFileStream* const cfs, + const u1* const record_attribute_start, + ConstantPool* cp, + u2* const record_params_count_ptr, + TRAPS); + void parse_classfile_attributes(const ClassFileStream* const cfs, ConstantPool* cp, ClassAnnotationCollector* parsed_annotations, @@ -487,6 +500,9 @@ FieldLayoutInfo* info, TRAPS); + // check that the current class is not extending a final class or interface + void check_subtyping(TRAPS); + void update_class_name(Symbol* new_name); public:
--- a/src/hotspot/share/classfile/vmSymbols.hpp Fri Jun 14 08:37:37 2019 +0200 +++ b/src/hotspot/share/classfile/vmSymbols.hpp Fri Jun 14 11:12:54 2019 +0200 @@ -159,6 +159,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") \ @@ -168,6 +169,7 @@ template(tag_runtime_invisible_type_annotations, "RuntimeInvisibleTypeAnnotations") \ template(tag_enclosing_method, "EnclosingMethod") \ template(tag_bootstrap_methods, "BootstrapMethods") \ + template(tag_permitted_subtypes, "PermittedSubtypes") \ \ /* exception klasses: at least all exceptions thrown by the VM have entries here */ \ template(java_lang_ArithmeticException, "java/lang/ArithmeticException") \
--- a/src/hotspot/share/include/jvm.h Fri Jun 14 08:37:37 2019 +0200 +++ b/src/hotspot/share/include/jvm.h Fri Jun 14 11:12:54 2019 +0200 @@ -517,6 +517,12 @@ JNIEXPORT jobjectArray JNICALL JVM_GetClassDeclaredConstructors(JNIEnv *env, jclass ofClass, jboolean publicOnly); +JNIEXPORT jobjectArray JNICALL +JVM_GetRecordParameters(JNIEnv *env, jclass ofClass); + +JNIEXPORT jint JNICALL +JVM_GetRecordParametersCount(JNIEnv *env, jclass ofClass); + /* 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 @@ -537,6 +543,9 @@ JNIEXPORT jobjectArray JNICALL JVM_GetNestMembers(JNIEnv *env, jclass current); +JNIEXPORT jobjectArray JNICALL +JVM_GetPermittedSubtypes(JNIEnv *env, jclass current); + /* The following two reflection routines are still needed due to startup time issues */ /* * java.lang.reflect.Method
--- a/src/hotspot/share/logging/logTag.hpp Fri Jun 14 08:37:37 2019 +0200 +++ b/src/hotspot/share/logging/logTag.hpp Fri Jun 14 11:12:54 2019 +0200 @@ -141,6 +141,7 @@ LOG_TAG(safepoint) \ LOG_TAG(sampling) \ LOG_TAG(scavenge) \ + LOG_TAG(sealed) \ LOG_TAG(setting) \ LOG_TAG(smr) \ LOG_TAG(stacktrace) \
--- a/src/hotspot/share/oops/instanceKlass.cpp Fri Jun 14 08:37:37 2019 +0200 +++ b/src/hotspot/share/oops/instanceKlass.cpp Fri Jun 14 11:12:54 2019 +0200 @@ -211,6 +211,60 @@ return false; } +// Called to verify that k is a permitted subtype of this class +bool InstanceKlass::has_as_permitted_subtype(InstanceKlass* k, TRAPS) const { + if (k == NULL) { + if (log_is_enabled(Trace, class, sealed)) { + ResourceMark rm(THREAD); + log_trace(class, sealed)("Checked for permitted subtype of %s with a NULL instance class", this->external_name()); + } + return false; + } + if (_permitted_subtypes == NULL || _permitted_subtypes == Universe::the_empty_short_array()) { + if (log_is_enabled(Trace, class, sealed)) { + ResourceMark rm(THREAD); + log_trace(class, sealed)("Checked for permitted subtype of %s in non-sealed class %s", + k->external_name(), this->external_name()); + } + return false; + } + + if (log_is_enabled(Trace, class, sealed)) { + ResourceMark rm(THREAD); + log_trace(class, sealed)("Checking for permitted subtype of %s in %s", + k->external_name(), this->external_name()); + } + + oop classloader1 = this->class_loader(); + oop classloader2 = k->class_loader(); + if (!oopDesc::equals(classloader1, classloader2)) { + log_trace(class, sealed)("Checked for same class loader of permitted subtype of %s and sealed class %s", + k->external_name(), this->external_name()); + return false; + } + + // Check for a resolved cp entry, else fall back to a name check. + // We don't want to resolve any class other than the one being checked. + for (int i = 0; i < _permitted_subtypes->length(); i++) { + int cp_index = _permitted_subtypes->at(i); + if (_constants->tag_at(cp_index).is_klass()) { + Klass* k2 = _constants->klass_at(cp_index, CHECK_false); + if (k2 == k) { + log_trace(class, sealed)("- class is listed at permitted_subtypes[%d] => cp[%d]", i, cp_index); + return true; + } + } else { + Symbol* name = _constants->klass_name_at(cp_index); + if (name == k->name()) { + log_trace(class, sealed)("- Found it at permitted_subtypes[%d] => cp[%d]", i, cp_index); + return true; + } + } + } + log_trace(class, sealed)("- class is NOT a permitted subtype!"); + return false; +} + // Return nest-host class, resolving, validating and saving it if needed. // In cases where this is called from a thread that can not do classloading // (such as a native JIT thread) then we simply return NULL, which in turn @@ -435,6 +489,7 @@ _nest_members(NULL), _nest_host_index(0), _nest_host(NULL), + _permitted_subtypes(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()), @@ -597,6 +652,19 @@ } set_nest_members(NULL); + if (record_params() != NULL && + record_params() != Universe::the_empty_short_array()) { + MetadataFactory::free_array<jushort>(loader_data, record_params()); + } + set_record_params(NULL, 0); + + if (permitted_subtypes() != NULL && + permitted_subtypes() != Universe::the_empty_short_array() && + !permitted_subtypes()->is_shared()) { + MetadataFactory::free_array<jushort>(loader_data, permitted_subtypes()); + } + set_permitted_subtypes(NULL); + // We should deallocate the Annotations instance if it's not in shared spaces. if (annotations() != NULL && !annotations()->is_shared()) { MetadataFactory::free_metadata(loader_data, annotations()); @@ -608,6 +676,12 @@ } } +bool InstanceKlass::is_sealed() const { + return _permitted_subtypes != NULL && + _permitted_subtypes != Universe::the_empty_short_array() && + _permitted_subtypes->length() > 0; +} + bool InstanceKlass::should_be_initialized() const { return !is_initialized(); } @@ -2313,6 +2387,8 @@ } it->push(&_nest_members); + it->push(&_record_params); + it->push(&_permitted_subtypes); } void InstanceKlass::remove_unshareable_info() { @@ -3228,6 +3304,7 @@ } 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(); + st->print(BULLET"permitted subtypes: "); permitted_subtypes()->print_value_on(st); st->cr(); if (java_mirror() != NULL) { st->print(BULLET"java mirror: "); java_mirror()->print_value_on(st);
--- a/src/hotspot/share/oops/instanceKlass.hpp Fri Jun 14 08:37:37 2019 +0200 +++ b/src/hotspot/share/oops/instanceKlass.hpp Fri Jun 14 11:12:54 2019 +0200 @@ -32,6 +32,7 @@ #include "oops/fieldInfo.hpp" #include "oops/instanceOop.hpp" #include "oops/klassVtable.hpp" +#include "oops/recordParamInfo.hpp" #include "runtime/handles.hpp" #include "runtime/os.hpp" #include "utilities/accessFlags.hpp" @@ -182,6 +183,10 @@ // By always being set it makes nest-member access checks simpler. InstanceKlass* _nest_host; + // The PermittedSubtypes attribute. An array of shorts, where each is a + // class info index for the class that is a permitted subtype. + Array<jushort>* _permitted_subtypes; + // 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 @@ -190,6 +195,8 @@ // if this class is unloaded. Symbol* _array_name; + Array<u2>* _record_params; + // Number of heapOopSize words used by non-static fields in this klass // (including inherited fields but after header_size()). int _nonstatic_field_size; @@ -202,6 +209,7 @@ u2 _source_file_name_index; u2 _static_oop_field_count;// number of static oop fields in this klass u2 _java_fields_count; // The number of declared Java fields + u2 _record_params_count; // The number of record parameters int _nonstatic_oop_map_size;// size in words of nonstatic oop map blocks int _itable_len; // length of Java itable (in words) @@ -418,6 +426,8 @@ friend class fieldDescriptor; FieldInfo* field(int index) const { return FieldInfo::from_field_array(_fields, index); } + RecordParamInfo* record_param(int index) const { return RecordParamInfo::from_record_params_array(_record_params, index); } + public: int field_offset (int index) const { return field(index)->offset(); } int field_access_flags(int index) const { return field(index)->access_flags(); } @@ -446,9 +456,31 @@ jushort nest_host_index() const { return _nest_host_index; } void set_nest_host_index(u2 i) { _nest_host_index = i; } + // record parameters + int record_param_access_flags(int index) const { return record_param(index)->access_flags(); } + Symbol* record_param_name(int index) const { return record_param(index)->name(constants()); } + Symbol* record_param_signature(int index) const { return record_param(index)->signature(constants()); } + Symbol* record_param_descriptor(int index) const { return record_param(index)->signature(constants()); } + + int record_params_count() const { return (int)_record_params_count; } + + Array<u2>* record_params() const { return _record_params; } + void set_record_params(Array<u2>* record_params, u2 record_params_count) { + guarantee(_record_params == NULL || record_params == NULL, "Just checking"); + _record_params = record_params; + _record_params_count = record_params_count; + } + +// permitted subtypes + Array<u2>* permitted_subtypes() const { return _permitted_subtypes; } + void set_permitted_subtypes(Array<u2>* s) { _permitted_subtypes = s; } + 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; + + // Called to verify that k is a permitted subtype of this class + bool has_as_permitted_subtype(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 @@ -506,6 +538,9 @@ ClassState init_state() { return (ClassState)_init_state; } bool is_rewritten() const { return (_misc_flags & _misc_rewritten) != 0; } + // is this a sealed class + bool is_sealed() const; + // defineClass specified verification bool should_verify_class() const { return (_misc_flags & _misc_should_verify_class) != 0;
--- a/src/hotspot/share/oops/klassVtable.cpp Fri Jun 14 08:37:37 2019 +0200 +++ b/src/hotspot/share/oops/klassVtable.cpp Fri Jun 14 11:12:54 2019 +0200 @@ -66,7 +66,7 @@ void klassVtable::compute_vtable_size_and_num_mirandas( int* vtable_length_ret, int* num_new_mirandas, GrowableArray<Method*>* all_mirandas, const Klass* super, - Array<Method*>* methods, AccessFlags class_flags, u2 major_version, + Array<Method*>* methods, AccessFlags class_flags, bool is_class_sealed, u2 major_version, Handle classloader, Symbol* classname, Array<InstanceKlass*>* local_interfaces, TRAPS) { NoSafepointVerifier nsv; @@ -83,7 +83,7 @@ assert(methods->at(i)->is_method(), "must be a Method*"); methodHandle mh(THREAD, methods->at(i)); - if (needs_new_vtable_entry(mh, super, classloader, classname, class_flags, major_version, THREAD)) { + if (needs_new_vtable_entry(mh, super, classloader, classname, class_flags, is_class_sealed, major_version, THREAD)) { assert(!methods->at(i)->is_private(), "private methods should not need a vtable entry"); vtable_length += vtableEntry::size(); // we need a new entry } @@ -397,7 +397,7 @@ return false; } - if (target_method->is_final_method(klass->access_flags())) { + if (target_method->is_final_method(klass->access_flags(), klass->is_sealed())) { // a final method never needs a new entry; final methods can be statically // resolved and they have to be present in the vtable only if they override // a super's method, in which case they re-use its entry @@ -584,6 +584,7 @@ Handle classloader, Symbol* classname, AccessFlags class_flags, + bool is_sealed, u2 major_version, TRAPS) { if (class_flags.is_interface()) { @@ -594,7 +595,7 @@ return false; } - if (target_method->is_final_method(class_flags) || + if (target_method->is_final_method(class_flags, is_sealed) || // a final method never needs a new entry; final methods can be statically // resolved and they have to be present in the vtable only if they override // a super's method, in which case they re-use its entry
--- a/src/hotspot/share/oops/klassVtable.hpp Fri Jun 14 08:37:37 2019 +0200 +++ b/src/hotspot/share/oops/klassVtable.hpp Fri Jun 14 11:12:54 2019 +0200 @@ -89,6 +89,7 @@ const Klass* super, Array<Method*>* methods, AccessFlags class_flags, + bool is_class_sealed, u2 major_version, Handle classloader, Symbol* classname, @@ -132,6 +133,7 @@ Handle classloader, Symbol* classname, AccessFlags access_flags, + bool is_sealed, u2 major_version, TRAPS);
--- a/src/hotspot/share/oops/method.cpp Fri Jun 14 08:37:37 2019 +0200 +++ b/src/hotspot/share/oops/method.cpp Fri Jun 14 11:12:54 2019 +0200 @@ -585,16 +585,16 @@ return _access_flags.has_loops(); } -bool Method::is_final_method(AccessFlags class_access_flags) const { +bool Method::is_final_method(AccessFlags class_access_flags, bool has_sealed_holder) const { // or "does_not_require_vtable_entry" // default method or overpass can occur, is not final (reuses vtable entry) // private methods in classes get vtable entries for backward class compatibility. if (is_overpass() || is_default_method()) return false; - return is_final() || class_access_flags.is_final(); + return is_final() || (class_access_flags.is_final() && !has_sealed_holder); } bool Method::is_final_method() const { - return is_final_method(method_holder()->access_flags()); + return is_final_method(method_holder()->access_flags(), method_holder()->is_sealed()); } bool Method::is_default_method() const { @@ -608,7 +608,7 @@ } bool Method::can_be_statically_bound(AccessFlags class_access_flags) const { - if (is_final_method(class_access_flags)) return true; + if (is_final_method(class_access_flags, method_holder()->is_sealed())) return true; #ifdef ASSERT ResourceMark rm; bool is_nonv = (vtable_index() == nonvirtual_vtable_index);
--- a/src/hotspot/share/oops/method.hpp Fri Jun 14 08:37:37 2019 +0200 +++ b/src/hotspot/share/oops/method.hpp Fri Jun 14 11:12:54 2019 +0200 @@ -630,7 +630,7 @@ // checks method and its method holder bool is_final_method() const; - bool is_final_method(AccessFlags class_access_flags) const; + bool is_final_method(AccessFlags class_access_flags, bool has_sealed_holder) const; // interface method declared with 'default' - excludes private interface methods bool is_default_method() const;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/oops/recordParamInfo.hpp Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018, 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_VM_OOPS_RECORDPARAMINFO_HPP +#define SHARE_VM_OOPS_RECORDPARAMINFO_HPP + +#include "oops/constantPool.hpp" +#include "oops/typeArrayOop.hpp" +#include "classfile/vmSymbols.hpp" + +// This class represents the parameter information contained in the recordParams +// array of an InstanceKlass. Currently it's laid on top an array of +// Java shorts but in the future it could simply be used as a real +// array type. RecordParamInfo generally shouldn't be used directly. +// Record parameters should be queried through InstanceKlass. + +class RecordParamInfo { + friend class ClassFileParser; + friend class RecordParameterStreamBase; + friend class JavaRecordParameterStream; + enum ParamOffset { + access_flags_offset = 0, + name_index_offset = 1, + descriptor_index_offset = 2, + signature_index_offset = 3, + param_slots = 4 + }; + +private: + u2 _shorts[param_slots]; + + void set_name_index(u2 val) { _shorts[name_index_offset] = val; } + void set_descriptor_index(u2 val) { _shorts[descriptor_index_offset] = val; } + void set_signature_index(u2 val) { _shorts[signature_index_offset] = val; } + + u2 name_index() const { return _shorts[name_index_offset]; } + u2 descriptor_index() const { return _shorts[descriptor_index_offset]; } + u2 signature_index() const { return _shorts[signature_index_offset]; } +public: + static RecordParamInfo* from_record_params_array(Array<u2>* record_params, int index) { + return ((RecordParamInfo*)record_params->adr_at(index * param_slots)); + } + static RecordParamInfo* from_record_params_array(u2* record_params, int index) { + return ((RecordParamInfo*)(record_params + index * param_slots)); + } + + void initialize(u2 access_flags, + u2 name_index, + u2 descriptor_index, + u2 signature_index) { + _shorts[access_flags_offset] = access_flags; + _shorts[name_index_offset] = name_index; + _shorts[descriptor_index_offset] = descriptor_index; + _shorts[signature_index_offset] = signature_index; + } + + u2 access_flags() const { return _shorts[access_flags_offset]; } + Symbol* name(const constantPoolHandle& cp) const { return cp->symbol_at(name_index()); } + Symbol* signature(const constantPoolHandle& cp) const { return cp->symbol_at(signature_index()); } + Symbol* descriptor(const constantPoolHandle& cp) const { return cp->symbol_at(descriptor_index()); } + void set_access_flags(u2 val) { _shorts[access_flags_offset] = val; } +}; + +#endif // SHARE_VM_OOPS_RECORDPARAMINFO_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/oops/recordParamStreams.hpp Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2018, 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_VM_OOPS_RECORDPARAMSTREAMS_HPP +#define SHARE_VM_OOPS_RECORDPARAMSTREAMS_HPP + +#include "oops/instanceKlass.hpp" +#include "oops/recordParamInfo.hpp" + +// The is the base class for iteration over the record parameters array +// describing the parameters in a record. +class RecordParameterStreamBase : public StackObj { + protected: + Array<u2>* _record_parameters; + constantPoolHandle _constants; + int _index; + int _limit; + + RecordParamInfo* record_param() const { return RecordParamInfo::from_record_params_array(_record_parameters, _index); } + InstanceKlass* record_param_holder() const { return _constants->pool_holder(); } + + RecordParameterStreamBase(Array<u2>* record_params, const constantPoolHandle& constants, int start, int limit) { + _record_parameters = record_params; + _constants = constants; + _index = start; + int num_record_parameters = record_params->length() / RecordParamInfo::param_slots; + if (limit < start) { + _limit = num_record_parameters; + } else { + _limit = limit; + } + } + + RecordParameterStreamBase(Array<u2>* record_params, const constantPoolHandle& constants) { + _record_parameters = record_params; + _constants = constants; + _index = 0; + _limit = record_params->length() / RecordParamInfo::param_slots;; + } + + public: + RecordParameterStreamBase(InstanceKlass* klass) { + _record_parameters = klass->record_params(); + _constants = klass->constants(); + _index = 0; + _limit = klass->record_params_count(); + assert(klass == record_param_holder(), ""); + } + + // accessors + int index() const { return _index; } + + void next() { + _index += 1; + } + bool done() const { return _index >= _limit; } + + // Accessors for current record parameter + AccessFlags access_flags() const { + AccessFlags flags; + flags.set_flags(record_param()->access_flags()); + return flags; + } + + void set_access_flags(u2 flags) const { + record_param()->set_access_flags(flags); + } + + void set_access_flags(AccessFlags flags) const { + set_access_flags(flags.as_short()); + } + + Symbol* name() const { + return record_param()->name(_constants); + } + + Symbol* descriptor() const { + return record_param()->descriptor(_constants); + } + + Symbol* signature() const { + return record_param()->signature(_constants); + } +}; + +// Iterate over the record parameters +class JavaRecordParameterStream : public RecordParameterStreamBase { + public: + JavaRecordParameterStream(const InstanceKlass* k): RecordParameterStreamBase(k->record_params(), k->constants(), 0, k->record_params_count()) {} + + int name_index() const { + return record_param()->name_index(); + } + void set_name_index(int index) { + record_param()->set_name_index(index); + } + int descriptor_index() const { + return record_param()->descriptor_index(); + } + void set_descriptor_index(int index) { + record_param()->set_descriptor_index(index); + } + int signature_index() const { + return record_param()->signature_index(); + } + void set_generic_signature_index(int index) { + record_param()->set_signature_index(index); + } +}; + +#endif // SHARE_VM_OOPS_RECORDPARAMSTREAMS_HPP
--- a/src/hotspot/share/prims/jvm.cpp Fri Jun 14 08:37:37 2019 +0200 +++ b/src/hotspot/share/prims/jvm.cpp Fri Jun 14 11:12:54 2019 +0200 @@ -53,6 +53,7 @@ #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" +#include "oops/recordParamStreams.hpp" #include "prims/jvm_misc.hpp" #include "prims/jvmtiExport.hpp" #include "prims/jvmtiThreadState.hpp" @@ -1660,6 +1661,63 @@ } JVM_END +JVM_ENTRY(jint, JVM_GetRecordParametersCount(JNIEnv *env, jclass ofClass)) +{ + // current is not a primitive or array class + JVMWrapper("JVM_GetRecordParametersCount"); + JvmtiVMObjectAllocEventCollector oam; + + InstanceKlass* k = InstanceKlass::cast(java_lang_Class::as_Klass(JNIHandles::resolve_non_null(ofClass))); + // Ensure class is linked + k->link_class(CHECK_0); + + return k->record_params_count(); +} +JVM_END + +JVM_ENTRY(jobjectArray, JVM_GetRecordParameters(JNIEnv *env, jclass ofClass)) +{ + // current is not a primitive or array class + JVMWrapper("JVM_GetRecordParameters"); + JvmtiVMObjectAllocEventCollector oam; + + InstanceKlass* k = InstanceKlass::cast(java_lang_Class::as_Klass(JNIHandles::resolve_non_null(ofClass))); + constantPoolHandle cp(THREAD, k->constants()); + + // Ensure class is linked + k->link_class(CHECK_NULL); + + // Allocate result + int num_record_params = k->record_params_count(); + Array<u2>* record_parameters = k->record_params(); + // DEBUG + //tty->print_cr("num_record_params == %d", num_record_params); + + if (num_record_params == 0) { + oop res = oopFactory::new_objArray(SystemDictionary::reflect_Field_klass(), 0, CHECK_NULL); + return (jobjectArray) JNIHandles::make_local(env, res); + } + + objArrayOop r = oopFactory::new_objArray(SystemDictionary::reflect_Field_klass(), num_record_params, CHECK_NULL); + objArrayHandle result (THREAD, r); + + int out_idx = 0; + fieldDescriptor fd; + for (JavaRecordParameterStream recordParamsStream(k); !recordParamsStream.done(); recordParamsStream.next()) { + for (JavaFieldStream fileStream(k); !fileStream.done(); fileStream.next()) { + if (fileStream.name() == recordParamsStream.name()) { + fd.reinitialize(k, fileStream.index()); + oop field = Reflection::new_field(&fd, CHECK_NULL); + result->obj_at_put(out_idx, field); + ++out_idx; + } + } + } + assert(out_idx == num_record_params, "just checking"); + 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()); @@ -1861,6 +1919,44 @@ } JVM_END +JVM_ENTRY(jobjectArray, JVM_GetPermittedSubtypes(JNIEnv* env, jclass current)) +{ + JVMWrapper("JVM_GetPermittedSubtypes"); + Klass* c = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(current)); + assert(c->is_instance_klass(), "must be"); + InstanceKlass* ck = InstanceKlass::cast(c); + Symbol* icce = vmSymbols::java_lang_IncompatibleClassChangeError(); + { + JvmtiVMObjectAllocEventCollector oam; + Array<u2>* subtypes = ck->permitted_subtypes(); + int length = subtypes == NULL ? 0 : subtypes->length(); + if (length == 0) { + return NULL; + } + objArrayOop r = oopFactory::new_objArray(SystemDictionary::Class_klass(), length, CHECK_NULL); + objArrayHandle result (THREAD, r); + int i; + for (i = 0; i < length; i++) { + int cp_index = subtypes->at(i); + Klass* k = ck->constants()->klass_at(cp_index, CHECK_NULL); + if (k->is_instance_klass()) { + result->obj_at_put(i, k->java_mirror()); + } else { + ResourceMark rm(THREAD); + Exceptions::fthrow(THREAD_AND_LOCATION, + icce, + "Class %s can not be a permitted subtype of %s", + k->external_name(), + ck->external_name() + ); + return NULL; + } + } + return (jobjectArray)JNIHandles::make_local(THREAD, result()); + } +} +JVM_END + // Constant pool access ////////////////////////////////////////////////////////// JVM_ENTRY(jobject, JVM_GetClassConstantPool(JNIEnv *env, jclass cls))
--- a/src/java.base/share/classes/java/lang/Class.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/java.base/share/classes/java/lang/Class.java Fri Jun 14 11:12:54 2019 +0200 @@ -2249,6 +2249,29 @@ return copyFields(privateGetDeclaredFields(false)); } + /** + * TBD + * @return TBD + * @throws SecurityException TBD + * @since 1.12 + */ + @CallerSensitive + public Field[] getRecordParameters() throws SecurityException { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true); + } + return isPrimitive() || isArray() ? new Field[0] : copyFields(privateGetRecordParameters()); + } + + /** + * Returns the number of record parameters if this class is a record, 0 if not + * @return the number of record parameters + * @since 1.12 + */ + public int getRecordParametersCount() { + return isPrimitive() || isArray() ? 0 : getRecordParametersCount0(); + } /** * Returns an array containing {@code Method} objects reflecting all the @@ -2950,6 +2973,8 @@ volatile Field[] declaredPublicFields; volatile Method[] declaredPublicMethods; volatile Class<?>[] interfaces; + // record parameters + volatile Field[] recordParameters; // Cached names String simpleName; @@ -3070,6 +3095,21 @@ return res; } + private Field[] privateGetRecordParameters() { + Field[] res; + ReflectionData<T> rd = reflectionData(); + if (rd != null) { + res = rd.recordParameters; + if (res != null) return res; + } + // No cached value available; request value from VM + res = Reflection.filterFields(this, getRecordParameters0()); + if (rd != null) { + rd.recordParameters = res; + } + return res; + } + // Returns an array of "root" fields. These Field objects must NOT // be propagated to the outside world, but must instead be copied // via ReflectionFactory.copyField. @@ -3407,6 +3447,8 @@ private native Method[] getDeclaredMethods0(boolean publicOnly); private native Constructor<T>[] getDeclaredConstructors0(boolean publicOnly); private native Class<?>[] getDeclaredClasses0(); + private native Field[] getRecordParameters0(); + private native int getRecordParametersCount0(); /** * Helper method to get the method name from arguments. @@ -3503,6 +3545,61 @@ 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() { + // we need to create a native method that checks if the Record attribute is present or not + return false; + } + + /** + * Returns an array with the names of the components + * + * @return an array with the names of the components + * @since 1.12 + */ + public String[] getRecordParameterNames() { + if (isRecord()) { + Field[] recordParameters = getRecordParameters(); + String[] names = new String[recordParameters.length]; + int i = 0; + for (Field field : recordParameters) { + names[i] = field.getName(); + i++; + } + return names; + } else { + return new String[0]; + } + } + + /** + * Returns an array with the types of the record parameters + * + * @return an array with the types of the record parameters + * @since 1.12 + */ + public Class<?>[] getRecordParameterTypes() { + if (isRecord()) { + Field[] recordParameters = getRecordParameters(); + Class<?>[] types = new Class<?>[recordParameters.length]; + int i = 0; + for (Field field : recordParameters) { + types[i] = field.getType(); + i++; + } + return types; + } else { + return new Class<?>[0]; + } + } + // Fetches the factory for reflective objects private static ReflectionFactory getReflectionFactory() { if (reflectionFactory == null) { @@ -4092,4 +4189,28 @@ public Optional<ClassDesc> describeConstable() { return Optional.of(ClassDesc.ofDescriptor(descriptorString())); } + + /** + * Returns an array containing {@code Class} objects representing all the permitted subtypes of this class + * if it is sealed. Returns an empty array if this class is not sealed. + * @return an array of all the permitted subtypes of this class + * @since 12 + */ + public Class<?>[] getPermittedSubtypes() { + Class<?>[] result = getPermittedSubtypes0(); + return (result == null) ? + new Class<?>[0] : + result; + } + + /** + * Returns true if this class or interface is sealed. + * @return returns true if the class or interface is sealed + * @since 12 + */ + public boolean isSealed() { + return Modifier.isFinal(getModifiers()) && getPermittedSubtypes().length != 0; + } + + private native Class<?>[] getPermittedSubtypes0(); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/java/lang/invoke/ObjectMethodBuilders.java Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,277 @@ +/* + * 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.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; + +/** + * ObjectMethodBuilders + * + * @author Brian Goetz + */ +public class ObjectMethodBuilders { + 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; + + 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", null)); + 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(ObjectMethodBuilders.class, "eq", MethodType.methodType(boolean.class, Object.class, Object.class)); + HASH_COMBINER = lookup.findStatic(ObjectMethodBuilders.class, "hashCombiner", MethodType.fromMethodDescriptorString("(II)I", null)); + primitiveEquals.put(byte.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(BB)Z", null))); + primitiveEquals.put(short.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(SS)Z", null))); + primitiveEquals.put(char.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(CC)Z", null))); + primitiveEquals.put(int.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(II)Z", null))); + primitiveEquals.put(long.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(JJ)Z", null))); + primitiveEquals.put(float.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(FF)Z", null))); + primitiveEquals.put(double.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(DD)Z", null))); + primitiveEquals.put(boolean.class, lookup.findStatic(ObjectMethodBuilders.class, "eq", MethodType.fromMethodDescriptorString("(ZZ)Z", null))); + + primitiveHashers.put(byte.class, lookup.findStatic(Byte.class, "hashCode", MethodType.fromMethodDescriptorString("(B)I", null))); + primitiveHashers.put(short.class, lookup.findStatic(Short.class, "hashCode", MethodType.fromMethodDescriptorString("(S)I", null))); + primitiveHashers.put(char.class, lookup.findStatic(Character.class, "hashCode", MethodType.fromMethodDescriptorString("(C)I", null))); + primitiveHashers.put(int.class, lookup.findStatic(Integer.class, "hashCode", MethodType.fromMethodDescriptorString("(I)I", null))); + primitiveHashers.put(long.class, lookup.findStatic(Long.class, "hashCode", MethodType.fromMethodDescriptorString("(J)I", null))); + primitiveHashers.put(float.class, lookup.findStatic(Float.class, "hashCode", MethodType.fromMethodDescriptorString("(F)I", null))); + primitiveHashers.put(double.class, lookup.findStatic(Double.class, "hashCode", MethodType.fromMethodDescriptorString("(D)I", null))); + primitiveHashers.put(boolean.class, lookup.findStatic(Boolean.class, "hashCode", MethodType.fromMethodDescriptorString("(Z)I", null))); + + 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 {@code equals}, {@code hashCode}, and {@code toString} methods for a given data class + * @param lookup the lookup + * @param methodName the method name + * @param type the descriptor type + * @param theClass the data class + * @param names the list of field names joined into a string, separated by ";" + * @param getters the list of getters + * @return a call site if invoked by and indy or a method handle if invoked by a condy + * @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": + // validate method type + handle = makeEquals(theClass, getterList); + return methodType != null ? new ConstantCallSite(handle) : handle; + case "hashCode": + // validate method type + handle = makeHashCode(theClass, getterList); + return methodType != null ? new ConstantCallSite(handle) : handle; + case "toString": + // validate method type + handle = makeToString(theClass, getterList, List.of(names.split(";"))); + return methodType != null ? new ConstantCallSite(handle) : handle; + default: + throw new IllegalArgumentException(methodName); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/java/lang/runtime/Extractor.java Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,562 @@ +/* + * Copyright (c) 2012, 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.runtime; + +import java.lang.invoke.CallSite; +import java.lang.invoke.ConstantCallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Objects; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import sun.invoke.util.BytecodeName; + +import static java.lang.invoke.MethodHandleInfo.REF_invokeInterface; +import static java.lang.invoke.MethodHandleInfo.REF_invokeStatic; +import static java.lang.invoke.MethodHandleInfo.REF_invokeVirtual; +import static java.lang.invoke.MethodHandleInfo.REF_newInvokeSpecial; +import static java.util.Objects.requireNonNull; + +/** + * Supporting type for implementation of pattern matching. An {@linkplain Extractor} + * is a constant bundle of method handles that describe a particular pattern, and + * suitable for storing in the constant pool. + * + * <p>An {@linkplain Extractor} is describe by a {@code descriptor} in the form + * of a {@link MethodType}. The return value of the descriptor is ignored; the + * argument types of the descriptor indicate the types of the output binding + * variables. + * + * Notes: + * - totality is erased; + * - compilers expected to optimize away total type patterns; + * - adaptation done in nest() and switch combinators + * + * @author Brian Goetz + */ +public interface Extractor { + /** + * A method handle that attempts to perform a match. It will have type + * {@code (Object)Object}. It accepts the target to be matched, and returns + * an opaque carrier if the match succeeds, or {@code null} if it fails. + * + * @return the {@code tryMatch} method handle + */ + MethodHandle tryMatch(); + + /** + * A method handle that extracts a component from the match carrier. It + * will take the match carrier and return the corresponding match binding. + * + * @param i the index of the component + * @return the {@code component} method handle + */ + MethodHandle component(int i); + + /** + * Returns the component method handles, as an array + * @return the component method handles + */ + MethodHandle[] components(); + + /** + * The descriptor of the {@linkplain Extractor}. The parameter types of + * the descriptor are the types of the binding variables. The return type + * is ignored. + * + * @return the descriptor + */ + MethodType descriptor(); + + /** + * Compose an extractor with a method handle that receives the bindings + * + * @param target method handle to receive the bindings + * @param sentinel value to return when the extractor does not match + * @return the composed method handle + */ + default MethodHandle compose(MethodHandle target, Object sentinel) { + int count = descriptor().parameterCount(); + MethodHandle[] components = components(); + Class<?> carrierType = tryMatch().type().returnType(); + Class<?> resultType = target.type().returnType(); + + MethodHandle mh = MethodHandles.filterArguments(target, 0, components); + mh = MethodHandles.permuteArguments(mh, MethodType.methodType(resultType, carrierType), new int[count]); + mh = MethodHandles.guardWithTest(ExtractorImpl.MH_OBJECTS_NONNULL.asType(MethodType.methodType(boolean.class, carrierType)), + mh, + MethodHandles.dropArguments(MethodHandles.constant(resultType, sentinel), 0, carrierType)); + mh = MethodHandles.filterArguments(mh, 0, tryMatch()); + return mh; + } + + private static MethodType descriptor(Class<?> targetType, MethodHandle[] components) { + Class<?>[] paramTypes = Stream.of(components) + .map(mh -> mh.type().returnType()) + .toArray(Class[]::new); + return MethodType.methodType(targetType, paramTypes); + } + + private static MethodHandle carrierTryExtract(MethodType descriptor, MethodHandle[] components) { + MethodHandle carrierFactory = ExtractorCarriers.carrierFactory(descriptor); + int[] reorder = new int[descriptor.parameterCount()]; // default value is what we want already + + return MethodHandles.permuteArguments(MethodHandles.filterArguments(carrierFactory, 0, components), + MethodType.methodType(carrierFactory.type().returnType(), descriptor.returnType()), + reorder); + } + + /** + * Construct a partial method handle that uses the predicate as guardWithTest, + * which applies the target if the test succeeds, and returns null if the + * test fails. The resulting method handle is of the same type as the + * {@code target} method handle. + * @param target + * @param predicate + * @return + */ + private static MethodHandle partialize(MethodHandle target, MethodHandle predicate) { + Class<?> targetType = target.type().parameterType(0); + Class<?> carrierType = target.type().returnType(); + return MethodHandles.guardWithTest(predicate, + target, + MethodHandles.dropArguments(MethodHandles.constant(carrierType, null), + 0, targetType)); + } + + /** + * Construct a method handle that delegates to target, unless the nth argument + * is null, in which case it returns null + */ + private static MethodHandle bailIfNthNull(MethodHandle target, int n) { + MethodHandle test = ExtractorImpl.MH_OBJECTS_ISNULL.asType(ExtractorImpl.MH_OBJECTS_ISNULL.type().changeParameterType(0, target.type().parameterType(n))); + test = MethodHandles.permuteArguments(test, target.type().changeReturnType(boolean.class), n); + MethodHandle nullh = MethodHandles.dropArguments(MethodHandles.constant(target.type().returnType(), null), 0, target.type().parameterArray()); + return MethodHandles.guardWithTest(test, nullh, target); + } + + /** + * Create a total {@linkplain Extractor} with the given descriptor, which + * operates by feeding results into a factory method handle and returning + * the result. + * + * @param descriptor the descriptor + * @param digester the digester method handle + * @return the extractor + */ + public static Extractor of(MethodType descriptor, + MethodHandle digester) { + return new ExtractorImpl(descriptor, + MethodHandles.insertArguments(digester, + 1, ExtractorCarriers.carrierFactory(descriptor)), + ExtractorCarriers.carrierComponents(descriptor)); + } + + /** + * Create a total {@linkplain Extractor} for a target of type {@code targetType} + * and a given set of component method handles. + * + * @param targetType The type of the match target + * @param components The component method handles + * @return the extractor + */ + public static Extractor ofTotal(Class<?> targetType, MethodHandle... components) { + MethodType descriptor = descriptor(targetType, components); + return new ExtractorImpl(descriptor, + carrierTryExtract(descriptor, components), + ExtractorCarriers.carrierComponents(descriptor)); + } + + /** + * Create a total {@linkplain Extractor} for a target of type {@code targetType} + * and a given set of component method handles, using itself as a carrier. + * + * @param targetType The type of the match target + * @param components The component method handles + * @return the extractor + */ + public static Extractor ofSelfTotal(Class<?> targetType, MethodHandle... components) { + return new ExtractorImpl(descriptor(targetType, components), + MethodHandles.identity(targetType), components); + } + + /** + * Create a partial {@linkplain Extractor} for a given set of component + * method handles. + * + * @param targetType the target type + * @param predicate The match predicate + * @param components The component method handles + * @return the extractor + */ + public static Extractor ofPartial(Class<?> targetType, MethodHandle predicate, MethodHandle... components) { + MethodType descriptor = descriptor(targetType, components); + MethodHandle carrierTryExtract = carrierTryExtract(descriptor, components); + return new ExtractorImpl(descriptor, + partialize(carrierTryExtract, predicate), + ExtractorCarriers.carrierComponents(descriptor)); + } + + /** + * Create a partial {@linkplain Extractor} for a given set of component + * method handles, using itself as a carrier. + * + * @param targetType the target type + * @param predicate The match predicate + * @param components The component method handles + * @return the extractor + */ + public static Extractor ofSelfPartial(Class<?> targetType, MethodHandle predicate, MethodHandle... components) { + return new ExtractorImpl(descriptor(targetType, components), + partialize(MethodHandles.identity(targetType), predicate), + components); + } + + /** + * Create an {@linkplain Extractor} for a type pattern, with a single binding + * variable, whose target type is {@code Object} + * + * @param type the type to match against + * @return the {@linkplain Extractor} + */ + public static Extractor ofType(Class<?> type) { + requireNonNull(type); + if (type.isPrimitive()) + throw new IllegalArgumentException("Reference type expected, found: " + type); + return new ExtractorImpl(MethodType.methodType(type, type), + ExtractorImpl.MH_OF_TYPE_HELPER.bindTo(type).asType(MethodType.methodType(type, type)), + MethodHandles.identity(type)); + } + + /** + * Create an {@linkplain Extractor} for a constant pattern + * + * @param o the constant + * @return the extractor + */ + public static Extractor ofConstant(Object o) { + Class<?> type = o == null ? Object.class : o.getClass(); + MethodHandle match = partialize(MethodHandles.dropArguments(MethodHandles.constant(Object.class, Boolean.TRUE), 0, type), + MethodHandles.insertArguments(ExtractorImpl.MH_OBJECTS_EQUAL, 0, o) + .asType(MethodType.methodType(boolean.class, type))); + return new ExtractorImpl(MethodType.methodType(type), match); + } + + /** + * Create an {@linkplain Extractor} for a nullable type pattern, with a + * single binding variable, whose target type is {@code Object} + * + * @param type the type to match against + * @return the {@linkplain Extractor} + */ + public static Extractor ofTypeNullable(Class<?> type) { + requireNonNull(type); + if (type.isPrimitive()) + throw new IllegalArgumentException("Reference type expected, found: " + type); + return new ExtractorImpl(MethodType.methodType(type, type), + ExtractorImpl.MH_OF_TYPE_NULLABLE_HELPER.bindTo(type).asType(MethodType.methodType(type, type)), + MethodHandles.identity(type)); + } + + /** + * Create an {@linkplain Extractor} that is identical to another {@linkplain Extractor}, + * but without the specified binding variables + * @param etor the original extractor + * @param positions which binding variables to drop + * @return the extractor + */ + public static Extractor dropBindings(Extractor etor, int... positions) { + MethodHandle[] mhs = etor.components(); + for (int position : positions) + mhs[position] = null; + mhs = Stream.of(mhs).filter(Objects::nonNull).toArray(MethodHandle[]::new); + return new ExtractorImpl(descriptor(etor.descriptor().returnType(), mhs), etor.tryMatch(), mhs); + } + + /** + * Adapt an extractor to a new target type + * + * @param e the extractor + * @param newTarget the new target type + * @return the new extractor + */ + public static Extractor adapt(Extractor e, Class<?> newTarget) { + if (e.descriptor().returnType().isAssignableFrom(newTarget)) + return e; + MethodHandle tryMatch = partialize(e.tryMatch().asType(e.tryMatch().type().changeParameterType(0, newTarget)), + ExtractorImpl.MH_ADAPT_HELPER.bindTo(e.descriptor().returnType()) + .asType(MethodType.methodType(boolean.class, newTarget))); + return new ExtractorImpl(e.descriptor().changeReturnType(newTarget), + tryMatch, e.components()); + } + + /** + * Construct a nested extractor, which first matches the target to the + * outer extractor, and then matches the resulting bindings to the inner + * extractors (if not null). The resulting extractor is partial if any + * of the input extractors are; its target type is the target type of the + * outer extractor; and its bindings are the concatenation of the bindings + * of the outer extractor followed by the bindings of the non-null inner + * extractors. + * + * @param outer The outer extractor + * @param extractors The inner extractors, or null if no nested extraction + * for this outer binding is desired + * @return the nested extractor + */ + public static Extractor ofNested(Extractor outer, Extractor... extractors) { + int outerCount = outer.descriptor().parameterCount(); + Class<?> outerCarrierType = outer.tryMatch().type().returnType(); + + // Adapt inners to types of outer bindings + for (int i = 0; i < extractors.length; i++) { + Extractor extractor = extractors[i]; + if (extractor.descriptor().returnType() != outer.descriptor().parameterType(i)) + extractors[i] = adapt(extractor, outer.descriptor().parameterType(i)); + } + + int[] innerPositions = IntStream.range(0, extractors.length) + .filter(i -> extractors[i] != null) + .toArray(); + MethodHandle[] innerComponents = Stream.of(extractors) + .filter(Objects::nonNull) + .map(Extractor::components) + .flatMap(Stream::of) + .toArray(MethodHandle[]::new); + MethodHandle[] innerTryMatches = Stream.of(extractors) + .filter(Objects::nonNull) + .map(e -> e.tryMatch()) + .toArray(MethodHandle[]::new); + Class<?>[] innerCarriers = Stream.of(extractors) + .filter(Objects::nonNull) + .map(e -> e.tryMatch().type().returnType()) + .toArray(Class[]::new); + Class<?>[] innerTypes = Stream.of(innerComponents) + .map(mh -> mh.type().returnType()) + .toArray(Class[]::new); + + MethodType descriptor = outer.descriptor().appendParameterTypes(innerTypes); + + MethodHandle mh = ExtractorCarriers.carrierFactory(descriptor); + mh = MethodHandles.filterArguments(mh, outerCount, innerComponents); + int[] spreadInnerCarriers = new int[outerCount + innerComponents.length]; + for (int i=0; i<outerCount; i++) + spreadInnerCarriers[i] = i; + int k = outerCount; + int j = 0; + for (Extractor e : extractors) { + if (e == null) + continue; + for (int i=0; i<e.descriptor().parameterCount(); i++) + spreadInnerCarriers[k++] = outerCount + j; + j++; + } + MethodType spreadInnerCarriersMT = outer.descriptor() + .appendParameterTypes(innerCarriers) + .changeReturnType(mh.type().returnType()); + mh = MethodHandles.permuteArguments(mh, spreadInnerCarriersMT, spreadInnerCarriers); + for (int position : innerPositions) + mh = bailIfNthNull(mh, outerCount + position); + mh = MethodHandles.filterArguments(mh, outerCount, innerTryMatches); + int[] spreadNestedCarrier = new int[outerCount + innerPositions.length]; + for (int i=0; i<outerCount; i++) + spreadNestedCarrier[i] = i; + for (int i=0; i<innerPositions.length; i++) + spreadNestedCarrier[outerCount+i] = innerPositions[i]; + mh = MethodHandles.permuteArguments(mh, outer.descriptor().changeReturnType(mh.type().returnType()), + spreadNestedCarrier); + mh = MethodHandles.filterArguments(mh, 0, outer.components()); + mh = MethodHandles.permuteArguments(mh, MethodType.methodType(mh.type().returnType(), outerCarrierType), + new int[outerCount]); + mh = bailIfNthNull(mh, 0); + mh = MethodHandles.filterArguments(mh, 0, outer.tryMatch()); + + MethodHandle tryExtract = mh; + + return new ExtractorImpl(descriptor, tryExtract, ExtractorCarriers.carrierComponents(descriptor)); + } + + + /** + * Bootstrap for creating a lazy, partial, self-carrier {@linkplain Extractor} from components + * + * @param lookup ignored + * @param constantName ignored + * @param constantType Must be {@code ()Extractor} + * @param descriptor the descriptor method type + * @param components the {@code components} method handles + * @return a callsite + * @throws Throwable doc + */ + public static CallSite makeLazyExtractor(MethodHandles.Lookup lookup, String constantName, MethodType constantType, + MethodType descriptor, MethodHandle... components) throws Throwable { + return new ConstantCallSite(MethodHandles.constant(Extractor.class, ofSelfTotal(descriptor.returnType(), components))); + } + + /** + * Condy bootstrap for creating lazy extractors + * + * @param lookup ignored + * @param constantName ignored + * @param constantType ignored + * @param descriptor the extractor descriptor + * @param components the extractor components + * @return the extractor factory + * @throws Throwable if something went wrong + */ + public static Extractor ofSelfTotal(MethodHandles.Lookup lookup, String constantName, Class<Extractor> constantType, + MethodType descriptor, MethodHandle... components) throws Throwable { + return ofSelfTotal(descriptor.returnType(), components); + } + + /** + * Condy bootstrap for creating nested extractors + * + * @param lookup ignored + * @param invocationName ignored + * @param invocationType must be {@code Class<Extractor>} + * @param outer the outer extractor + * @param inners the inner extractors, null if no nesting is needed for this binding + * @return the nested extractor + */ + public static Extractor ofNested(MethodHandles.Lookup lookup, String invocationName, Class<Extractor> invocationType, + Extractor outer, Extractor... inners) { + return ofNested(outer, inners); + } + + /** + * Condy bootstrap for creating non-nullable type extractor + * + * @param lookup ignored + * @param invocationName ignored + * @param invocationType must be {@code Class<Extractor>} + * @param type the type + * @return the extractor + */ + public static Extractor ofType(MethodHandles.Lookup lookup, String invocationName, Class<Extractor> invocationType, + Class<?> type) { + return ofType(type); + } + + /** + * Condy bootstrap for creating nullable type extractor + * + * @param lookup ignored + * @param invocationName ignored + * @param invocationType must be {@code Class<Extractor>} + * @param type the type + * @return the extractor + */ + public static Extractor ofTypeNullable(MethodHandles.Lookup lookup, String invocationName, Class<Extractor> invocationType, + Class<?> type) { + return ofTypeNullable(type); + } + + /** + * Condy bootstrap for creating constant extractor + * + * @param lookup ignored + * @param invocationName ignored + * @param invocationType must be {@code Class<Extractor>} + * @param constant the constant + * @return the extractor + */ + public static Extractor ofConstant(MethodHandles.Lookup lookup, String invocationName, Class<Extractor> invocationType, + Object constant) { + return ofConstant(constant); + } + + /** + * Condy bootstrap for finding extractors + * + * @param lookup the lookup context + * @param constantName ignored + * @param constantType ignored + * @param owner the class containing the extractor + * @param descriptor the extractor descriptor + * @param name the extractor name + * @param refKind the kind of method + * @return the extractor + * @throws Throwable if something went wrong + */ + public static Extractor findExtractor(MethodHandles.Lookup lookup, String constantName, Class<Extractor> constantType, + Class<?> owner, MethodType descriptor, String name, int refKind) throws Throwable { + String dd = descriptor.toMethodDescriptorString(); + String patternMethodName + = BytecodeName.toBytecodeName(String.format("$pattern$%s$%s", + (refKind == REF_newInvokeSpecial ? owner.getSimpleName() : name), + dd.substring(0, dd.indexOf(')') + 1))); + MethodType factoryDesc = MethodType.methodType(Extractor.class); + MethodHandle mh; + switch (refKind) { + case REF_invokeStatic: + case REF_newInvokeSpecial: + mh = lookup.findStatic(owner, patternMethodName, factoryDesc); + break; + case REF_invokeVirtual: + case REF_invokeInterface: + mh = lookup.findVirtual(owner, patternMethodName, factoryDesc); + break; + default: + throw new IllegalAccessException(Integer.toString(refKind)); + } + + return (Extractor) mh.invoke(); + } + + /** + * Bootstrap for extracting the {@code tryMatch} method handle from a {@linkplain Extractor} + * + * @param lookup ignored + * @param constantName ignored + * @param constantType Must be {@code MethodHandle.class} + * @param extractor the {@linkplain Extractor} + * @return the {@code tryMatch} method handle + */ + + + public static MethodHandle extractorTryMatch(MethodHandles.Lookup lookup, String constantName, Class<MethodHandle> constantType, + Extractor extractor) { + return extractor.tryMatch(); + } + + /** + * Bootstrap for extracting a {@code component} method handle from a {@linkplain Extractor} + * + * @param lookup ignored + * @param constantName ignored + * @param constantType Must be {@code MethodHandle.class} + * @param extractor the {@linkplain Extractor} + * @param i the component index + * @return the {@code component} method handle + */ + public static MethodHandle extractorComponent(MethodHandles.Lookup lookup, String constantName, Class<MethodHandle> constantType, + Extractor extractor, int i) { + return extractor.component(i); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/java/lang/runtime/ExtractorCarriers.java Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2012, 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.runtime; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Arrays; + +/** + * PatternCarriers + */ +public class ExtractorCarriers { + + private static final CarrierFactory factory = CarrierFactories.DUMB; + + interface CarrierFactory { + MethodHandle constructor(MethodType methodType); + MethodHandle component(MethodType methodType, int component); + } + + static class DumbCarrier { + private final Object[] args; + + DumbCarrier(Object... args) { + this.args = args.clone(); + } + + Object get(int i) { + return args[i]; + } + } + + enum CarrierFactories implements CarrierFactory { + DUMB { + private final MethodHandle CARRIER_CTOR; + private final MethodHandle CARRIER_GET; + + { + try { + CARRIER_CTOR = MethodHandles.lookup().findConstructor(DumbCarrier.class, MethodType.methodType(void.class, Object[].class)); + CARRIER_GET = MethodHandles.lookup().findVirtual(DumbCarrier.class, "get", MethodType.methodType(Object.class, int.class)); + } + catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + @Override + public MethodHandle constructor(MethodType methodType) { + return CARRIER_CTOR.asType(methodType.changeReturnType(Object.class)); + } + + @Override + public MethodHandle component(MethodType methodType, int component) { + return MethodHandles.insertArguments(CARRIER_GET, 1, component) + .asType(MethodType.methodType(methodType.parameterType(component), Object.class)); + } + }, + DUMB_SINGLE { + // An optimization of DUMB, where we use the value itself as carrier when there is only one value + + @Override + public MethodHandle constructor(MethodType methodType) { + return methodType.parameterCount() == 1 ? MethodHandles.identity(methodType.parameterType(0)) : DUMB.constructor(methodType); + } + + @Override + public MethodHandle component(MethodType methodType, int component) { + return methodType.parameterCount() == 1 ? MethodHandles.identity(methodType.parameterType(0)) : DUMB.component(methodType, component); + } + } + } + + /** + * Returns a method handle with the given method type that instantiates + * a new carrier object. + * + * @param methodType the types of the carrier elements + * @return the carrier factory + */ + public static MethodHandle carrierFactory(MethodType methodType) { + return factory.constructor(methodType); + } + + /** + * Returns a method handle that accepts a carrier and returns the i'th component + * + * @param methodType the type of the carrier elements + * @param i the index of the component + * @return the component method handle + */ + public static MethodHandle carrierComponent(MethodType methodType, int i) { + return factory.component(methodType, i); + } + + /** + * Return all the components method handles for a carrier + * @param methodType the type of the carrier elements + * @return the component method handles + */ + public static MethodHandle[] carrierComponents(MethodType methodType) { + MethodHandle[] components = new MethodHandle[methodType.parameterCount()]; + Arrays.setAll(components, i -> factory.component(methodType, i)); + return components; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/java/lang/runtime/ExtractorImpl.java Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2012, 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.runtime; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.List; +import java.util.Objects; + +/** + * Non-public implementation of {@link Extractor} + */ +class ExtractorImpl implements Extractor { + private final MethodType descriptor; + private final MethodHandle tryMatch; + private final List<MethodHandle> components; + + // These are helpers for Extractors + static final MethodHandle MH_OF_TYPE_HELPER; + static final MethodHandle MH_OF_TYPE_NULLABLE_HELPER; + static final MethodHandle MH_ADAPT_HELPER; + static final MethodHandle MH_OBJECTS_ISNULL; + static final MethodHandle MH_OBJECTS_NONNULL; + static final MethodHandle MH_OBJECTS_EQUAL; + static { + try { + MH_OF_TYPE_HELPER = MethodHandles.lookup().findStatic(ExtractorImpl.class, "ofTypeHelper", MethodType.methodType(Object.class, Class.class, Object.class)); + MH_OF_TYPE_NULLABLE_HELPER = MethodHandles.lookup().findStatic(ExtractorImpl.class, "ofTypeNullableHelper", MethodType.methodType(Object.class, Class.class, Object.class)); + MH_ADAPT_HELPER = MethodHandles.lookup().findStatic(ExtractorImpl.class, "adaptHelper", MethodType.methodType(boolean.class, Class.class, Object.class)); + MH_OBJECTS_ISNULL = MethodHandles.lookup().findStatic(Objects.class, "isNull", MethodType.methodType(boolean.class, Object.class)); + MH_OBJECTS_NONNULL = MethodHandles.lookup().findStatic(Objects.class, "nonNull", MethodType.methodType(boolean.class, Object.class)); + MH_OBJECTS_EQUAL = MethodHandles.lookup().findStatic(Objects.class, "equals", MethodType.methodType(boolean.class, Object.class, Object.class)); + } + catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + /** + * Construct an {@link Extractor} from components + * Constraints: + * - output of tryMatch must match input of components + * - input of tryMatch must match descriptor + * - output of components must match descriptor + * + * @param descriptor The {@code descriptor} method type + * @param tryMatch The {@code tryMatch} method handle + * @param components The {@code component} method handles + */ + ExtractorImpl(MethodType descriptor, MethodHandle tryMatch, MethodHandle... components) { + Class<?> carrierType = tryMatch.type().returnType(); + if (descriptor.parameterCount() != components.length) + throw new IllegalArgumentException(String.format("MethodType %s arity should match component count %d", descriptor, components.length)); + if (!descriptor.returnType().equals(tryMatch.type().parameterType(0))) + throw new IllegalArgumentException(String.format("Descriptor %s should match tryMatch input %s", descriptor, tryMatch.type())); + for (int i = 0; i < components.length; i++) { + MethodHandle component = components[i]; + if (component.type().parameterCount() != 1 + || component.type().returnType().equals(void.class) + || !component.type().parameterType(0).equals(carrierType)) + throw new IllegalArgumentException("Invalid component descriptor " + component.type()); + if (!component.type().returnType().equals(descriptor.parameterType(i))) + throw new IllegalArgumentException(String.format("Descriptor %s should match %d'th component %s", descriptor, i, component)); + } + + this.descriptor = descriptor; + this.tryMatch = tryMatch; + this.components = List.of(components); + } + + @Override + public MethodHandle tryMatch() { + return tryMatch; + } + + @Override + public MethodHandle component(int i) { + return components.get(i); + } + + @Override + public MethodHandle[] components() { + return components.toArray(new MethodHandle[0]); + } + + @Override + public MethodType descriptor() { + return descriptor; + } + + private static Object ofTypeHelper(Class<?> type, Object o) { + return o != null && type.isAssignableFrom(o.getClass()) ? o : null; + } + + private static Object ofTypeNullableHelper(Class<?> type, Object o) { + return o == null || type.isAssignableFrom(o.getClass()) ? o : null; + } + + private static boolean adaptHelper(Class<?> type, Object o) { + return o != null && type.isAssignableFrom(o.getClass()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/java/lang/runtime/PatternSim.java Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,362 @@ +/* + * Copyright (c) 2012, 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.runtime; + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Temporary scaffolding to allow matching / switching on constrained patterns + * without language support. + */ +public class PatternSim { + private static String DEFAULT_LABEL = ""; + + /** + * Simulator for statement switch + * @param label the switch label + * @param target the switch target + * @return the switch simulator + */ + public static StatementSwitch _switch(String label, Object target) { + return new SwitchImpl(target, label); + } + + /** + * Simulator for statement switch + * @param target the switch target + * @return the switch simulator + */ + public static StatementSwitch _switch(Object target) { + return new SwitchImpl(target, DEFAULT_LABEL); + } + + /** + * Simulator for expression switch + * @param label the switch label + * @param target the switch target + * @param <T> the the return type + * @return the switch simulator + */ + public static<T> ExpressionSwitch<T> _expswitch(String label, Object target) { + return new ExprSwitchImpl<>(target, label); + } + + /** + * Simulator for expression switch + * @param target the switch target + * @param <T> the the return type + * @return the switch simulator + */ + public static<T> ExpressionSwitch<T> _expswitch(Object target) { + return new ExprSwitchImpl<>(target, DEFAULT_LABEL); + } + + /** + * Simulator for continuing out of a switch + */ + public static void _continue() { + throw new ContinueSignal(DEFAULT_LABEL); + } + + /** + * Simulator for continuing out of the labeled switch + * + * @param label the label of the switch to continue at + */ + public static void _continue(String label) { + throw new ContinueSignal(label); + } + + /** + * Simulator type for statement switch + */ + public interface StatementSwitch { + /** + * Simulate a case of a statement switch + * @param pattern the pattern to match against + * @param action the success action + * @param <B> the type of the binding variable + * @return the switch + */ + <B> StatementSwitch _case(Supplier<_pattern<B>> pattern, Consumer<B> action); + + /** + * Simulate a case of a statement switch + * @param pattern the pattern to match against + * @param action the success action + * @param <B> the type of the binding variable + * @return the switch + */ + <B> StatementSwitch _case(Supplier<_pattern<B>> pattern, Runnable action); + + /** + * Simulate the default clause of a statement switch + * @param r the action + * @return the switch + */ + StatementSwitch _default(Runnable r); + } + + /** + * Simulator type for expression switch + * @param <T> the switch type + */ + public interface ExpressionSwitch<T> { + /** + * Simulate a case of an expression switch + * @param pattern the pattern to match against + * @param action the success action + * @param <B> the type of the binding variable + * @return the switch + */ + <B> ExpressionSwitch<T> _case(Supplier<_pattern<B>> pattern, Function<B, T> action); + + /** + * Simulate a case of an expression switch + * @param pattern the pattern to match against + * @param action the success action + * @param <B> the type of the binding variable + * @return the switch + */ + <B> ExpressionSwitch<T> _case(Supplier<_pattern<B>> pattern, Supplier<T> action); + + /** + * Simulate the default clause of an expression switch + * @param r the action + * @return the switch + */ + ExpressionSwitch<T> _default(Supplier<T> r); + + /** + * Get the result of an expression switch + * @return the result + */ + T result(); + } + + /** + * Helper method for nested pattern in a statement switch + * @param target the nested target + * @param pattern the nested pattern + * @param action the success action + * @param <B> the type of the nested target + */ + public static<B> void _nest(Object target, Supplier<_pattern<B>> pattern, Consumer<B> action) { + Optional<B> match = pattern.get().match(target); + if (match.isPresent()) + action.accept(match.get()); + else + throw new ContinueSignal("<$nested$>"); + } + + /** + * Helper method for nested pattern in a statement switch + * @param target the nested target + * @param pattern the nested pattern + * @param action the success action + * @param <B> the type of the nested target + */ + public static<B> void _nest(Object target, Supplier<_pattern<B>> pattern, Runnable action) { + Optional<B> match = pattern.get().match(target); + if (match.isPresent()) + action.run(); + else + throw new ContinueSignal("<$nested$>"); + } + + /** + * Helper method for nested pattern in an expression switch + * @param target the nested target + * @param pattern the nested pattern + * @param action the success action + * @param <B> the type of the nested target + * @param <T> the return type of the success action + * @return the return value of the success action + */ + public static<B, T> T _expnest(Object target, Supplier<_pattern<B>> pattern, Function<B, T> action) { + Optional<B> match = pattern.get().match(target); + if (match.isPresent()) + return action.apply(match.get()); + else + throw new ContinueSignal("<$nested$>"); + } + + /** + * Helper method for nested pattern in an expression switch + * @param target the nested target + * @param pattern the nested pattern + * @param action the success action + * @param <B> the type of the nested target + * @param <T> the return type of the success action + * @return the return value of the success action + */ + public static<B, T> T _expnest(Object target, Supplier<_pattern<B>> pattern, Supplier<T> action) { + Optional<B> match = pattern.get().match(target); + if (match.isPresent()) + return action.get(); + else + throw new ContinueSignal("<$nested$>"); + } + + @SuppressWarnings("serial") + static class ContinueSignal extends RuntimeException { + String label; + + ContinueSignal(String label) { + super(); + this.label = label; + } + + void maybeRethrow(String label) { + if (!this.label.equals(label)) + throw this; + } + } +} + +class SwitchImpl implements PatternSim.StatementSwitch { + private final Object target; + private final String label; + private boolean done = false; + + SwitchImpl(Object target, + String label) { + this.target = target; + this.label = label; + } + + public <B> PatternSim.StatementSwitch _case(Supplier<_pattern<B>> pattern, Consumer<B> action) { + if (!done) { + Optional<B> match = pattern.get().match(target); + if (match.isPresent()) { + try { + action.accept(match.get()); + done = true; + } + catch (PatternSim.ContinueSignal signal) { + signal.maybeRethrow(label); + } + } + } + return this; + } + + public <B> PatternSim.StatementSwitch _case(Supplier<_pattern<B>> pattern, Runnable action) { + if (!done) { + Optional<B> match = pattern.get().match(target); + if (match.isPresent()) { + try { + action.run(); + done = true; + } + catch (PatternSim.ContinueSignal signal) { + signal.maybeRethrow(label); + } + } + } + return this; + } + + public PatternSim.StatementSwitch _default(Runnable r) { + if (!done) { + try { + r.run(); + done = true; + } + catch (PatternSim.ContinueSignal signal) { + signal.maybeRethrow(label); + } + } + return this; + } + +} + +class ExprSwitchImpl<T> implements PatternSim.ExpressionSwitch<T> { + private final Object target; + private final String label; + private boolean done = false; + private T result = null; + + ExprSwitchImpl(Object target, + String label) { + this.target = target; + this.label = label; + } + + public<B> PatternSim.ExpressionSwitch<T> _case(Supplier<_pattern<B>> pattern, Function<B, T> action) { + if (!done) { + Optional<B> match = pattern.get().match(target); + if (match.isPresent()) { + try { + result = action.apply(match.get()); + done = true; + } + catch (PatternSim.ContinueSignal signal) { + signal.maybeRethrow(label); + } + } + } + return this; + } + + @Override + public <B> PatternSim.ExpressionSwitch<T> _case(Supplier<_pattern<B>> pattern, Supplier<T> action) { + if (!done) { + Optional<B> match = pattern.get().match(target); + if (match.isPresent()) { + try { + result = action.get(); + done = true; + } + catch (PatternSim.ContinueSignal signal) { + signal.maybeRethrow(label); + } + } + } + return this; + } + + public PatternSim.ExpressionSwitch<T> _default(Supplier<T> r) { + if (!done) { + try { + result = r.get(); + done = true; + } + catch (PatternSim.ContinueSignal signal) { + signal.maybeRethrow(label); + } + } + return this; + } + + public T result() { + return result; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,882 @@ +/* + * 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.runtime; + +import java.lang.invoke.CallSite; +import java.lang.invoke.ConstantCallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +import static java.util.Objects.requireNonNull; + +/** + * Bootstrap methods for linking {@code invokedynamic} call sites that implement + * the selection functionality of the {@code switch} statement. The bootstraps + * take additional static arguments corresponding to the {@code case} labels + * of the {@code switch}, implicitly numbered sequentially from {@code [0..N)}. + * + * <p>The bootstrap call site accepts a single parameter of the type of the + * operand of the {@code switch}, and return an {@code int} that is the index of + * the matched {@code case} label, {@code -1} if the target is {@code null}, + * or {@code N} if the target is not null but matches no {@code case} label. + */ +public class SwitchBootstraps { + + // Shared INIT_HOOK for all switch call sites; looks the target method up in a map + private static final MethodHandle CONSTANT_INIT_HOOK; + private static final MethodHandle PATTERN_INIT_HOOK; + private static final MethodHandle TYPE_INIT_HOOK; + private static final MethodHandle PATTERN_SWITCH_METHOD; + private static final MethodHandle TYPE_SWITCH_METHOD; + private static final Map<Class<?>, MethodHandle> switchMethods = new ConcurrentHashMap<>(); + + private static final Set<Class<?>> BOOLEAN_TYPES + = Set.of(boolean.class, Boolean.class); + // Types that can be handled as int switches + private static final Set<Class<?>> INT_TYPES + = Set.of(int.class, short.class, byte.class, char.class, + Integer.class, Short.class, Byte.class, Character.class); + private static final Set<Class<?>> FLOAT_TYPES + = Set.of(float.class, Float.class); + private static final Set<Class<?>> LONG_TYPES + = Set.of(long.class, Long.class); + private static final Set<Class<?>> DOUBLE_TYPES + = Set.of(double.class, Double.class); + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + private static final Function<Class<?>, MethodHandle> lookupSwitchMethod = + new Function<>() { + @Override + public MethodHandle apply(Class<?> c) { + try { + Class<?> switchClass; + if (c == Enum.class) + switchClass = EnumSwitchCallSite.class; + else if (c == String.class) + switchClass = StringSwitchCallSite.class; + else if (BOOLEAN_TYPES.contains(c) || INT_TYPES.contains(c) || + FLOAT_TYPES.contains(c)) + switchClass = IntSwitchCallSite.class; + else if (LONG_TYPES.contains(c) || DOUBLE_TYPES.contains(c)) + switchClass = LongSwitchCallSite.class; + else if (c == Object.class) + switchClass = TypeSwitchCallSite.class; + else + throw new BootstrapMethodError("Invalid switch type: " + c); + + return LOOKUP.findVirtual(switchClass, "doSwitch", + MethodType.methodType(int.class, c)); + } + catch (ReflectiveOperationException e) { + throw new BootstrapMethodError("Invalid switch type: " + c); + } + } + }; + + static { + try { + CONSTANT_INIT_HOOK = LOOKUP.findStatic(SwitchBootstraps.class, "constantInitHook", + MethodType.methodType(MethodHandle.class, CallSite.class)); + PATTERN_INIT_HOOK = LOOKUP.findStatic(SwitchBootstraps.class, "patternInitHook", + MethodType.methodType(MethodHandle.class, CallSite.class)); + TYPE_INIT_HOOK = LOOKUP.findStatic(SwitchBootstraps.class, "typeInitHook", + MethodType.methodType(MethodHandle.class, CallSite.class)); + PATTERN_SWITCH_METHOD = LOOKUP.findVirtual(PatternSwitchCallSite.class, "doSwitch", + MethodType.methodType(PatternSwitchResult.class, Object.class)); + TYPE_SWITCH_METHOD = LOOKUP.findVirtual(TypeSwitchCallSite.class, "doSwitch", + MethodType.methodType(int.class, Object.class)); + } + catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + private static<T extends CallSite> MethodHandle constantInitHook(T receiver) { + return switchMethods.computeIfAbsent(receiver.type().parameterType(0), lookupSwitchMethod) + .bindTo(receiver); + } + + private static<T extends CallSite> MethodHandle typeInitHook(T receiver) { + return TYPE_SWITCH_METHOD.bindTo(receiver); + } + + private static<T extends CallSite> MethodHandle patternInitHook(T receiver) { + return PATTERN_SWITCH_METHOD.bindTo(receiver); + } + + /** + * Bootstrap method for linking an {@code invokedynamic} call site that + * implements a {@code switch} on a {@code boolean} or {@code Boolean}. + * The static arguments are a varargs array of {@code boolean} labels, + * + * <p>The results are undefined if the labels array contains duplicates. + * + * @implNote + * + * The implementation only enforces the requirement that the labels array + * be duplicate-free if system assertions are enabled. + * + * @param lookup Represents a lookup context with the accessibility + * privileges of the caller. When used with {@code invokedynamic}, + * this is stacked automatically by the VM. + * @param invocationName The invocation name, which is ignored. When used with + * {@code invokedynamic}, this is provided by the + * {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param invocationType The invocation type of the {@code CallSite}. This + * method type should have a single parameter which is + * {@code boolean} or {@code Boolean},and return {@code int}. + * When used with {@code invokedynamic}, this is provided by + * the {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param booleanLabels boolean values corresponding to the case labels of the + * {@code switch} statement. + * @return the index into {@code booleanLabels} of the target value, if the target + * matches any of the labels, {@literal -1} if the target value is + * {@code null}, or {@code booleanLabels.length} if the target value does + * not match any of the labels. + * @throws NullPointerException if any required argument is null + * @throws IllegalArgumentException if the invocation type is not + * {@code (boolean)int} or {@code (Boolean)int} + * @throws Throwable if there is any error linking the call site + */ + public static CallSite booleanSwitch(MethodHandles.Lookup lookup, + String invocationName, + MethodType invocationType, + boolean... booleanLabels) throws Throwable { + if (invocationType.parameterCount() != 1 + || (!invocationType.returnType().equals(int.class)) + || (!BOOLEAN_TYPES.contains(invocationType.parameterType(0)))) + throw new IllegalArgumentException("Illegal invocation type " + invocationType); + requireNonNull(booleanLabels); + + int[] intLabels = IntStream.range(0, booleanLabels.length) + .map(i -> booleanLabels[i] ? 1 : 0) + .toArray(); + + assert IntStream.of(intLabels).distinct().count() == intLabels.length + : "switch labels are not distinct: " + Arrays.toString(booleanLabels); + + return new IntSwitchCallSite(invocationType, intLabels); + } + + /** + * Bootstrap method for linking an {@code invokedynamic} call site that + * implements a {@code switch} on an {@code int}, {@code short}, {@code byte}, + * {@code char}, or one of their box types. The static arguments are a + * varargs array of {@code int} labels. + * + * <p>The results are undefined if the labels array contains duplicates. + * + * @implNote + * + * The implementation only enforces the requirement that the labels array + * be duplicate-free if system assertions are enabled. + * + * @param lookup Represents a lookup context with the accessibility + * privileges of the caller. When used with {@code invokedynamic}, + * this is stacked automatically by the VM. + * @param invocationName The invocation name, which is ignored. When used with + * {@code invokedynamic}, this is provided by the + * {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param invocationType The invocation type of the {@code CallSite}. This + * method type should have a single parameter which is + * one of the 32-bit or shorter primitive types, or + * one of their box types, and return {@code int}. When + * used with {@code invokedynamic}, this is provided by + * the {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param intLabels integral values corresponding to the case labels of the + * {@code switch} statement. + * @return the index into {@code intLabels} of the target value, if the target + * matches any of the labels, {@literal -1} if the target value is + * {@code null}, or {@code intLabels.length} if the target value does + * not match any of the labels. + * @throws NullPointerException if any required argument is null + * @throws IllegalArgumentException if the invocation type is not + * {@code (T)int}, where {@code T} is one of the 32-bit or smaller integral + * primitive types, or one of their box types + * @throws Throwable if there is any error linking the call site + */ + public static CallSite intSwitch(MethodHandles.Lookup lookup, + String invocationName, + MethodType invocationType, + int... intLabels) throws Throwable { + if (invocationType.parameterCount() != 1 + || (!invocationType.returnType().equals(int.class)) + || (!INT_TYPES.contains(invocationType.parameterType(0)))) + throw new IllegalArgumentException("Illegal invocation type " + invocationType); + requireNonNull(intLabels); + + assert IntStream.of(intLabels).distinct().count() == intLabels.length + : "switch labels are not distinct: " + Arrays.toString(intLabels); + + return new IntSwitchCallSite(invocationType, intLabels); + } + + /** + * Bootstrap method for linking an {@code invokedynamic} call site that + * implements a {@code switch} on an {@code float} or {@code Float}. + * The static arguments are a varargs array of {@code float} labels. + * + * <p>The results are undefined if the labels array contains duplicates + * according to {@link Float#floatToIntBits(float)}. + * + * @implNote + * + * The implementation only enforces the requirement that the labels array + * be duplicate-free if system assertions are enabled. + * + * @param lookup Represents a lookup context with the accessibility + * privileges of the caller. When used with {@code invokedynamic}, + * this is stacked automatically by the VM. + * @param invocationName The invocation name, which is ignored. When used with + * {@code invokedynamic}, this is provided by the + * {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param invocationType The invocation type of the {@code CallSite}. This + * method type should have a single parameter which is + * one of the 32-bit or shorter primitive types, or + * one of their box types, and return {@code int}. When + * used with {@code invokedynamic}, this is provided by + * the {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param floatLabels float values corresponding to the case labels of the + * {@code switch} statement. + * @return the index into {@code floatLabels} of the target value, if the target + * matches any of the labels, {@literal -1} if the target value is + * {@code null}, or {@code floatLabels.length} if the target value does + * not match any of the labels. + * @throws NullPointerException if any required argument is null + * @throws IllegalArgumentException if the invocation type is not + * {@code (float)int} or {@code (Float)int} + * @throws Throwable if there is any error linking the call site + */ + public static CallSite floatSwitch(MethodHandles.Lookup lookup, + String invocationName, + MethodType invocationType, + float... floatLabels) throws Throwable { + if (invocationType.parameterCount() != 1 + || (!invocationType.returnType().equals(int.class)) + || (!FLOAT_TYPES.contains(invocationType.parameterType(0)))) + throw new IllegalArgumentException("Illegal invocation type " + invocationType); + requireNonNull(floatLabels); + + int[] intLabels = new int[floatLabels.length]; + for (int i=0; i<floatLabels.length; i++) + intLabels[i] = Float.floatToIntBits(floatLabels[i]); + + assert IntStream.of(intLabels).distinct().count() == intLabels.length + : "switch labels are not distinct: " + Arrays.toString(floatLabels); + + return new IntSwitchCallSite(invocationType, intLabels); + } + + static class IntSwitchCallSite extends ConstantCallSite { + private final int[] labels; + private final int[] indexes; + + IntSwitchCallSite(MethodType targetType, + int[] intLabels) throws Throwable { + super(targetType, CONSTANT_INIT_HOOK); + + // expensive way to index an array + indexes = IntStream.range(0, intLabels.length) + .boxed() + .sorted(Comparator.comparingInt(a -> intLabels[a])) + .mapToInt(Integer::intValue) + .toArray(); + labels = new int[indexes.length]; + for (int i=0; i<indexes.length; i++) + labels[i] = intLabels[indexes[i]]; + } + + int doSwitch(int target) { + int index = Arrays.binarySearch(labels, target); + return (index >= 0) ? indexes[index] : indexes.length; + } + + int doSwitch(boolean target) { + return doSwitch(target ? 1 : 0); + } + + int doSwitch(float target) { + return doSwitch(Float.floatToIntBits(target)); + } + + int doSwitch(short target) { + return doSwitch((int) target); + } + + int doSwitch(byte target) { + return doSwitch((int) target); + } + + int doSwitch(char target) { + return doSwitch((int) target); + } + + int doSwitch(Boolean target) { + return (target == null) ? -1 : doSwitch((boolean) target); + } + + int doSwitch(Integer target) { + return (target == null) ? -1 : doSwitch((int) target); + } + + int doSwitch(Float target) { + return (target == null) ? -1 : doSwitch((float) target); + } + + int doSwitch(Short target) { + return (target == null) ? -1 : doSwitch((int) target); + } + + int doSwitch(Character target) { + return (target == null) ? -1 : doSwitch((int) target); + } + + int doSwitch(Byte target) { + return (target == null) ? -1 : doSwitch((int) target); + } + } + + /** + * Bootstrap method for linking an {@code invokedynamic} call site that + * implements a {@code switch} on a {@code long} or {@code Long}. + * The static arguments are a varargs array of {@code long} labels. + * + * <p>The results are undefined if the labels array contains duplicates. + * + * @implNote + * + * The implementation only enforces the requirement that the labels array + * be duplicate-free if system assertions are enabled. + * + * @param lookup Represents a lookup context with the accessibility + * privileges of the caller. When used with {@code invokedynamic}, + * this is stacked automatically by the VM. + * @param invocationName The invocation name, which is ignored. When used with + * {@code invokedynamic}, this is provided by the + * {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param invocationType The invocation type of the {@code CallSite}. This + * method type should have a single parameter which is + * one of the 32-bit or shorter primitive types, or + * one of their box types, and return {@code int}. When + * used with {@code invokedynamic}, this is provided by + * the {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param longLabels long values corresponding to the case labels of the + * {@code switch} statement. + * @return the index into {@code longLabels} of the target value, if the target + * matches any of the labels, {@literal -1} if the target value is + * {@code null}, or {@code longLabels.length} if the target value does + * not match any of the labels. + * @throws NullPointerException if any required argument is null + * @throws IllegalArgumentException if the invocation type is not + * {@code (long)int} or {@code (Long)int} + * @throws Throwable if there is any error linking the call site + */ + public static CallSite longSwitch(MethodHandles.Lookup lookup, + String invocationName, + MethodType invocationType, + long... longLabels) throws Throwable { + if (invocationType.parameterCount() != 1 + || (!invocationType.returnType().equals(int.class)) + || (!LONG_TYPES.contains(invocationType.parameterType(0)))) + throw new IllegalArgumentException("Illegal invocation type " + invocationType); + requireNonNull(longLabels); + + assert LongStream.of(longLabels).distinct().count() == longLabels.length + : "switch labels are not distinct: " + Arrays.toString(longLabels); + + return new LongSwitchCallSite(invocationType, longLabels); + } + + /** + * Bootstrap method for linking an {@code invokedynamic} call site that + * implements a {@code switch} on a {@code double} or {@code Double}. + * The static arguments are a varargs array of {@code double} labels. + * + * <p>The results are undefined if the labels array contains duplicates + * according to {@link Double#doubleToLongBits(double)}. + * + * @implNote + * + * The implementation only enforces the requirement that the labels array + * be duplicate-free if system assertions are enabled. + * + * @param lookup Represents a lookup context with the accessibility + * privileges of the caller. When used with {@code invokedynamic}, + * this is stacked automatically by the VM. + * @param invocationName The invocation name, which is ignored. When used with + * {@code invokedynamic}, this is provided by the + * {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param invocationType The invocation type of the {@code CallSite}. This + * method type should have a single parameter which is + * one of the 32-bit or shorter primitive types, or + * one of their box types, and return {@code int}. When + * used with {@code invokedynamic}, this is provided by + * the {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param doubleLabels long values corresponding to the case labels of the + * {@code switch} statement. + * @return the index into {@code doubleLabels} of the target value, if the target + * matches any of the labels, {@literal -1} if the target value is + * {@code null}, or {@code doubleLabels.length} if the target value does + * not match any of the labels. + * @throws NullPointerException if any required argument is null + * @throws IllegalArgumentException if the invocation type is not + * {@code (double)int} or {@code (Double)int} + * @throws Throwable if there is any error linking the call site + */ + public static CallSite doubleSwitch(MethodHandles.Lookup lookup, + String invocationName, + MethodType invocationType, + double... doubleLabels) throws Throwable { + if (invocationType.parameterCount() != 1 + || (!invocationType.returnType().equals(int.class)) + || (!DOUBLE_TYPES.contains(invocationType.parameterType(0)))) + throw new IllegalArgumentException("Illegal invocation type " + invocationType); + requireNonNull(doubleLabels); + + long[] longLabels = new long[doubleLabels.length]; + for (int i=0; i<doubleLabels.length; i++) + longLabels[i] = Double.doubleToLongBits(doubleLabels[i]); + + assert LongStream.of(longLabels).distinct().count() == longLabels.length + : "switch labels are not distinct: " + Arrays.toString(doubleLabels); + + return new LongSwitchCallSite(invocationType, longLabels); + } + + static class LongSwitchCallSite extends ConstantCallSite { + private final long[] labels; + private final int[] indexes; + + LongSwitchCallSite(MethodType targetType, + long[] longLabels) throws Throwable { + super(targetType, CONSTANT_INIT_HOOK); + + // expensive way to index an array + indexes = IntStream.range(0, longLabels.length) + .boxed() + .sorted(Comparator.comparingLong(a -> longLabels[a])) + .mapToInt(Integer::intValue) + .toArray(); + labels = new long[indexes.length]; + for (int i=0; i<indexes.length; i++) + labels[i] = longLabels[indexes[i]]; + } + + int doSwitch(long target) { + int index = Arrays.binarySearch(labels, target); + return (index >= 0) ? indexes[index] : indexes.length; + } + + int doSwitch(double target) { + return doSwitch(Double.doubleToLongBits(target)); + } + + int doSwitch(Long target) { + return (target == null) ? -1 : doSwitch((long) target); + } + + int doSwitch(Double target) { + return (target == null) ? -1 : doSwitch((double) target); + } + } + + /** + * Bootstrap method for linking an {@code invokedynamic} call site that + * implements a {@code switch} on a {@code String} target. The static + * arguments are a varargs array of {@code String} labels. + * + * <p>The results are undefined if the labels array contains duplicates + * according to {@link String#equals(Object)}. + * + * @implNote + * + * The implementation only enforces the requirement that the labels array + * be duplicate-free if system assertions are enabled. + * + * @param lookup Represents a lookup context with the accessibility + * privileges of the caller. When used with {@code invokedynamic}, + * this is stacked automatically by the VM. + * @param invocationName The invocation name, which is ignored. When used with + * {@code invokedynamic}, this is provided by the + * {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param invocationType The invocation type of the {@code CallSite}. This + * method type should have a single parameter of + * {@code String}, and return {@code int}. When + * used with {@code invokedynamic}, this is provided by + * the {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param stringLabels non-null string values corresponding to the case + * labels of the {@code switch} statement. + * @return the index into {@code labels} of the target value, if the target + * matches any of the labels, {@literal -1} if the target value is + * {@code null}, or {@code stringLabels.length} if the target value + * does not match any of the labels. + * @throws NullPointerException if any required argument is null + * @throws IllegalArgumentException if any labels are null, or if the + * invocation type is not {@code (String)int} + * @throws Throwable if there is any error linking the call site + */ + public static CallSite stringSwitch(MethodHandles.Lookup lookup, + String invocationName, + MethodType invocationType, + String... stringLabels) throws Throwable { + if (invocationType.parameterCount() != 1 + || (!invocationType.returnType().equals(int.class)) + || (!invocationType.parameterType(0).equals(String.class))) + throw new IllegalArgumentException("Illegal invocation type " + invocationType); + requireNonNull(stringLabels); + if (Stream.of(stringLabels).anyMatch(Objects::isNull)) + throw new IllegalArgumentException("null label found"); + + assert Stream.of(stringLabels).distinct().count() == stringLabels.length + : "switch labels are not distinct: " + Arrays.toString(stringLabels); + + return new StringSwitchCallSite(invocationType, stringLabels); + } + + static class StringSwitchCallSite extends ConstantCallSite { + private static final Comparator<String> STRING_BY_HASH + = Comparator.comparingInt(Objects::hashCode); + + private final String[] sortedByHash; + private final int[] indexes; + private final boolean collisions; + + StringSwitchCallSite(MethodType targetType, + String[] stringLabels) throws Throwable { + super(targetType, CONSTANT_INIT_HOOK); + + // expensive way to index an array + indexes = IntStream.range(0, stringLabels.length) + .boxed() + .sorted(Comparator.comparingInt(i -> stringLabels[i].hashCode())) + .mapToInt(Integer::intValue) + .toArray(); + sortedByHash = new String[indexes.length]; + for (int i=0; i<indexes.length; i++) + sortedByHash[i] = stringLabels[indexes[i]]; + + collisions = IntStream.range(0, sortedByHash.length-1) + .anyMatch(i -> sortedByHash[i].hashCode() == sortedByHash[i + 1].hashCode()); + } + + int doSwitch(String target) { + if (target == null) + return -1; + + int index = Arrays.binarySearch(sortedByHash, target, STRING_BY_HASH); + if (index < 0) + return indexes.length; + else if (target.equals(sortedByHash[index])) { + return indexes[index]; + } + else if (collisions) { + int hash = target.hashCode(); + while (index > 0 && sortedByHash[index-1].hashCode() == hash) + --index; + for (; index < sortedByHash.length && sortedByHash[index].hashCode() == hash; index++) + if (target.equals(sortedByHash[index])) + return indexes[index]; + } + + return indexes.length; + } + } + + /** + * Bootstrap method for linking an {@code invokedynamic} call site that + * implements a {@code switch} on an {@code Enum} target. The static + * arguments are the enum class, and a varargs arrays of {@code String} + * that are the names of the enum constants corresponding to the + * {@code case} labels. + * + * <p>The results are undefined if the names array contains duplicates. + * + * @implNote + * + * The implementation only enforces the requirement that the labels array + * be duplicate-free if system assertions are enabled. + * + * @param <E> the enum type + * @param lookup Represents a lookup context with the accessibility + * privileges of the caller. When used with {@code invokedynamic}, + * this is stacked automatically by the VM. + * @param invocationName The invocation name, which is ignored. When used with + * {@code invokedynamic}, this is provided by the + * {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param invocationType The invocation type of the {@code CallSite}. This + * method type should have a single parameter of + * {@code Enum}, and return {@code int}. When + * used with {@code invokedynamic}, this is provided by + * the {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param enumClass the enum class + * @param enumNames names of the enum constants against which the target + * should be matched + * @return the index into {@code labels} of the target value, if the target + * matches any of the labels, {@literal -1} if the target value is + * {@code null}, or {@code stringLabels.length} if the target value + * does not match any of the labels. + * @throws IllegalArgumentException if the specified class is not an + * enum class, or any label name is null, + * or if the invocation type is not + * {@code (Enum)int} + * @throws NullPointerException if any required argument is null + * @throws Throwable if there is any error linking the call site + */ + public static<E extends Enum<E>> CallSite enumSwitch(MethodHandles.Lookup lookup, + String invocationName, + MethodType invocationType, + Class<E> enumClass, + String... enumNames) throws Throwable { + if (invocationType.parameterCount() != 1 + || (!invocationType.returnType().equals(int.class)) + || (!invocationType.parameterType(0).equals(Enum.class))) + throw new IllegalArgumentException("Illegal invocation type " + invocationType); + requireNonNull(enumClass); + requireNonNull(enumNames); + if (!enumClass.isEnum()) + throw new IllegalArgumentException("not an enum class"); + if (Stream.of(enumNames).anyMatch(Objects::isNull)) + throw new IllegalArgumentException("null label found"); + + assert Stream.of(enumNames).distinct().count() == enumNames.length + : "switch labels are not distinct: " + Arrays.toString(enumNames); + + return new EnumSwitchCallSite<>(invocationType, enumClass, enumNames); + } + + static class EnumSwitchCallSite<E extends Enum<E>> extends ConstantCallSite { + private final int[] ordinalMap; + + EnumSwitchCallSite(MethodType targetType, + Class<E> enumClass, + String... enumNames) throws Throwable { + super(targetType, CONSTANT_INIT_HOOK); + + ordinalMap = new int[enumClass.getEnumConstants().length]; + Arrays.fill(ordinalMap, enumNames.length); + + for (int i=0; i<enumNames.length; i++) { + try { + ordinalMap[E.valueOf(enumClass, enumNames[i]).ordinal()] = i; + } + catch (Exception e) { + // allow non-existent labels, but never match them + continue; + } + } + } + + @SuppressWarnings("rawtypes") + int doSwitch(Enum target) { + return (target == null) ? -1 : ordinalMap[target.ordinal()]; + } + } + + /** + * Bootstrap method for linking an {@code invokedynamic} call site that + * implements a {@code switch} on a reference-typed target. The static + * arguments are a varargs array of {@code Class} labels. + * + * @param lookup Represents a lookup context with the accessibility + * privileges of the caller. When used with {@code invokedynamic}, + * this is stacked automatically by the VM. + * @param invocationName The invocation name, which is ignored. When used with + * {@code invokedynamic}, this is provided by the + * {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param invocationType The invocation type of the {@code CallSite}. This + * method type should have a single parameter of + * a reference type, and return {@code int}. When + * used with {@code invokedynamic}, this is provided by + * the {@code NameAndType} of the {@code InvokeDynamic} + * structure and is stacked automatically by the VM. + * @param types non-null {@link Class} values + * @return the index into {@code labels} of the target value, if the target + * is an instance of any of the types, {@literal -1} if the target + * value is {@code null}, or {@code types.length} if the target value + * is not an instance of any of the types + * @throws NullPointerException if any required argument is null + * @throws IllegalArgumentException if any labels are null, or if the + * invocation type is not {@code (T)int for some reference type {@code T}} + * @throws Throwable if there is any error linking the call site + */ + public static CallSite typeSwitch(MethodHandles.Lookup lookup, + String invocationName, + MethodType invocationType, + Class<?>... types) throws Throwable { + if (invocationType.parameterCount() != 1 + || (!invocationType.returnType().equals(int.class)) + || invocationType.parameterType(0).isPrimitive()) + throw new IllegalArgumentException("Illegal invocation type " + invocationType); + requireNonNull(types); + + types = types.clone(); + if (Stream.of(types).anyMatch(Objects::isNull)) + throw new IllegalArgumentException("null label found"); + + assert Stream.of(types).distinct().count() == types.length + : "switch labels are not distinct: " + Arrays.toString(types); + + return new TypeSwitchCallSite(invocationType, types); + } + + static class TypeSwitchCallSite extends ConstantCallSite { + private final Class<?>[] types; + + TypeSwitchCallSite(MethodType targetType, + Class<?>[] types) throws Throwable { + super(targetType, TYPE_INIT_HOOK); + this.types = types; + } + + int doSwitch(Object target) { + if (target == null) + return -1; + + // Dumbest possible strategy + Class<?> targetClass = target.getClass(); + for (int i = 0; i < types.length; i++) { + Class<?> c = types[i]; + if (c.isAssignableFrom(targetClass)) + return i; + } + + return types.length; + } + } + + /** + * Result type for pattern switches + */ + public static class PatternSwitchResult { + /** + * The selected index, -1 if input was null, or length if not matched + */ + public final int index; + + /** + * The carrier + */ + public final Object carrier; + + /** + * Construct a PatternSwitchResult + * + * @param index the index + * @param carrier the carrier + */ + public PatternSwitchResult(int index, Object carrier) { + this.index = index; + this.carrier = carrier; + } + } + + /** + * Bootstrap for pattern switches + * + * @param lookup the lookup (ignored) + * @param invocationName the invocation name (ignored) + * @param invocationType the invocation type (must return PatternSwitchResult) + * @param patterns the patterns + * @return the result + * @throws Throwable if something went wrong + */ + public static CallSite patternSwitch(MethodHandles.Lookup lookup, + String invocationName, + MethodType invocationType, + Extractor... patterns) throws Throwable { + if (invocationType.parameterCount() != 1 + || (!invocationType.returnType().equals(PatternSwitchResult.class)) + || invocationType.parameterType(0).isPrimitive()) + throw new IllegalArgumentException("Illegal invocation type " + invocationType); + requireNonNull(patterns); + + patterns = patterns.clone(); + Class<?> targetType = invocationType.parameterType(0); + + for (int i = 0; i < patterns.length; i++) { + Extractor pattern = patterns[i]; + if (pattern.descriptor().returnType() != targetType) + patterns[i] = Extractor.adapt(pattern, targetType); + } + + if (Stream.of(patterns).anyMatch(Objects::isNull)) + throw new IllegalArgumentException("null pattern found"); + + return new PatternSwitchCallSite(invocationType, patterns); + } + + static class PatternSwitchCallSite extends ConstantCallSite { + private final Extractor[] patterns; + + PatternSwitchCallSite(MethodType targetType, + Extractor[] patterns) throws Throwable { + super(targetType, PATTERN_INIT_HOOK); + this.patterns = patterns; + } + + PatternSwitchResult doSwitch(Object target) throws Throwable { + if (target == null) + return new PatternSwitchResult(-1, null); + + // Dumbest possible strategy + for (int i = 0; i < patterns.length; i++) { + Extractor e = patterns[i]; + Object o = e.tryMatch().invoke(target); + if (o != null) + return new PatternSwitchResult(i, o); + } + + return new PatternSwitchResult(patterns.length, null); + + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/java/lang/runtime/_pattern.java Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2012, 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.runtime; + +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Temporary scaffolding to allow declaration of constrained patterns + * without language support. + */ +public interface _pattern<B> { + /** + * Attempt to match + * @param o the target + * @return the result, or an empty optional + */ + Optional<B> match(Object o); + + /** + * Construct a PatternDecl for a partial pattern + * @param predicate the applicability test + * @param extract the extraction logic + * @param <T> the type of a successful target + * @param <B> the type of the binding + * @return the PatternDecl + */ + @SuppressWarnings("unchecked") + static<T, B> _pattern<B> of(Predicate<T> predicate, Function<T, B> extract) { + return (Object o) -> + (predicate.test((T) o)) + ? Optional.of(extract.apply((T) o)) + : Optional.empty(); + } + + /** + * Construct a PatternDecl for a total pattern on Object + * @param extract the extraction logic + * @param <B> the type of the binding + * @return the PatternDecl + */ + static<B> _pattern<B> of(Function<?, B> extract) { + return of(o -> true, extract); + } + + /** + * Construct a PatternDecl for a type test pattern + * @param clazz The type to test against + * @param extract the extraction logic + * @param <T> the type of a successful target + * @param <B> the type of the binding + * @return the PatternDecl + */ + static<T, B> _pattern<B> ofType(Class<T> clazz, Function<T, B> extract) { + return of(o -> clazz.isAssignableFrom(o.getClass()), + o -> extract.apply(clazz.cast(o))); + } + + /** + * Construct a PatternDecl for a type test pattern + * @param clazz The type to test against + * @param <T> the type of a successful target + * @return the PatternDecl + */ + static<T> _pattern<T> ofType(Class<T> clazz) { + return of(o -> clazz.isAssignableFrom(o.getClass()), clazz::cast); + } + + /** + * Construct a PatternDecl for a constant + * @param constant the constant + * @param <T> the type of the constant + * @return the PatternDecl + */ + static<T> _pattern<T> ofConstant(T constant) { + return of(constant::equals, o -> constant); + } +}
--- a/src/java.base/share/classes/module-info.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/java.base/share/classes/module-info.java Fri Jun 14 11:12:54 2019 +0200 @@ -84,6 +84,7 @@ exports java.lang.module; exports java.lang.ref; exports java.lang.reflect; + exports java.lang.runtime; exports java.math; exports java.net; exports java.net.spi; @@ -129,10 +130,11 @@ exports javax.security.auth.x500; exports javax.security.cert; - // 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/native/libjava/Class.c Fri Jun 14 08:37:37 2019 +0200 +++ b/src/java.base/share/native/libjava/Class.c Fri Jun 14 11:12:54 2019 +0200 @@ -76,6 +76,9 @@ {"getRawTypeAnnotations", "()" BA, (void *)&JVM_GetClassTypeAnnotations}, {"getNestHost0", "()" CLS, (void *)&JVM_GetNestHost}, {"getNestMembers0", "()[" CLS, (void *)&JVM_GetNestMembers}, + {"getPermittedSubtypes0", "()[" CLS, (void *)&JVM_GetPermittedSubtypes}, + {"getRecordParameters0", "()[" FLD, (void *)&JVM_GetRecordParameters}, + {"getRecordParametersCount0", "()I", (void *)&JVM_GetRecordParametersCount}, }; #undef OBJ
--- a/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/java.compiler/share/classes/javax/lang/model/element/Element.java Fri Jun 14 11:12:54 2019 +0200 @@ -148,6 +148,11 @@ * parameter}, {@linkplain ExecutableElement the executable * element} which declares the parameter is returned. * + * <li> If this is a {@linkplain + * VariableElement#getEnclosingElement state component}, + * {@linkplain ExecutableElement the type} which declares the + * state component is returned. + * * <li> If this is a {@linkplain ModuleElement#getEnclosingElement * module}, {@code null} is returned. * @@ -166,7 +171,7 @@ * * A {@linkplain TypeElement#getEnclosedElements class or * interface} is considered to enclose the fields, methods, - * constructors, and member types that it directly declares. + * constructors, state 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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/java.compiler/share/classes/javax/lang/model/element/ElementKind.java Fri Jun 14 11:12:54 2019 +0200 @@ -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,29 @@ * @since 9 * @spec JPMS */ - MODULE; + MODULE, + /** + * A record type. + * @since amber + */ + RECORD, + + // Neither fish nor fowl; necessary? + /** + * A state component of a {@code record}. + * @since amber + */ + STATE_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/Modifier.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/java.compiler/share/classes/javax/lang/model/element/Modifier.java Fri Jun 14 11:12:54 2019 +0200 @@ -59,6 +59,11 @@ */ DEFAULT, /** The modifier {@code static} */ STATIC, + /** + * The modifier {@code sealed} + * @since amber + */ + SEALED, // Not sure this the best order; certainly after public/private. /** The modifier {@code final} */ FINAL, /** The modifier {@code transient} */ TRANSIENT, /** The modifier {@code volatile} */ VOLATILE,
--- a/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 class esand an annotation type is a kind of * interface. * * <p> While a {@code TypeElement} represents a class or interface @@ -82,7 +82,7 @@ TypeMirror asType(); /** - * Returns the fields, methods, constructors, and member types + * Returns the fields, methods, constructors, state components, and member types * that are directly declared in this class or interface. * * This includes any {@linkplain Elements.Origin#MANDATED @@ -178,6 +178,38 @@ List<? extends TypeParameterElement> getTypeParameters(); /** + * Returns the state components of this type element in + * declaration order. + * + * @implSpec The default implementations of this method returns an + * empty and unmodifiable list. + * + * @return the state components, or an empty list if there are + * none + * + * @since amber + */ + default List<? extends VariableElement> getStateComponents() { + 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/VariableElement.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/java.compiler/share/classes/javax/lang/model/element/VariableElement.java Fri Jun 14 11:12:54 2019 +0200 @@ -31,8 +31,8 @@ /** * Represents a field, {@code enum} constant, method or constructor - * parameter, local variable, resource variable, or exception - * parameter. + * parameter, local variable, resource variable, exception + * parameter, or state component. * * @author Joseph D. Darcy * @author Scott Seligman @@ -98,6 +98,9 @@ * The enclosing element of a method or constructor parameter is * the executable declaring the parameter. * + * The enclosing element of a state component is the type + * declaring the state component. + * * @return the enclosing element of this variable */ @Override
--- a/src/java.compiler/share/classes/javax/lang/model/element/package-info.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/java.compiler/share/classes/javax/lang/model/element/package-info.java Fri Jun 14 11:12:54 2019 +0200 @@ -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,
--- a/src/java.compiler/share/classes/javax/lang/model/util/ElementFilter.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementFilter.java Fri Jun 14 11:12:54 2019 +0200 @@ -83,7 +83,12 @@ Collections.unmodifiableSet(EnumSet.of(ElementKind.CLASS, ElementKind.ENUM, ElementKind.INTERFACE, + ElementKind.RECORD, ElementKind.ANNOTATION_TYPE)); + + private static final Set<ElementKind> STATE_COMPONENT_KIND = + Set.of(ElementKind.STATE_COMPONENT); + /** * Returns a list of fields in {@code elements}. * @return a list of fields in {@code elements} @@ -104,6 +109,17 @@ return setFilter(elements, FIELD_KINDS, VariableElement.class); } + // Method below may only be temporary + /** + * Returns a list of state descriptions in {@code elements}. + * @return a list of state descriptions in {@code elements} + * @param elements the elements to filter + */ + public static List<VariableElement> + stateComponentsIn(List<? extends Element> elements) { + return listFilter(elements, STATE_COMPONENT_KIND, VariableElement.class); + } + /** * Returns a list of constructors in {@code elements}. * @return a list of constructors in {@code elements}
--- a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor6.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor6.java Fri Jun 14 11:12:54 2019 +0200 @@ -154,6 +154,9 @@ case INTERFACE: return visitTypeAsInterface(e, p); + case RECORD: + return visitTypeAsRecord(e, p); + default: throw new AssertionError("Bad kind " + k + " for TypeElement" + e); } @@ -212,12 +215,28 @@ } /** + * 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 * the specific {@linkplain ElementKind kind} of variable, {@code * ENUM_CONSTANT}, {@code EXCEPTION_PARAMETER}, {@code FIELD}, - * {@code LOCAL_VARIABLE}, {@code PARAMETER}, or {@code RESOURCE_VARIABLE}. + * {@code LOCAL_VARIABLE}, {@code PARAMETER}, {@code RESOURCE_VARIABLE}, + * or {@code STATE_COMPONENT}. * * @param e {@inheritDoc} * @param p {@inheritDoc} @@ -245,6 +264,9 @@ case RESOURCE_VARIABLE: return visitVariableAsResourceVariable(e, p); + case STATE_COMPONENT: + return visitVariableAsStateComponent(e, p); + default: throw new AssertionError("Bad kind " + k + " for VariableElement" + e); } @@ -331,6 +353,21 @@ } /** + * Visits a {@code STATE_COMPONENT} variable 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 visitVariableAsStateComponent(VariableElement e, P p) { + return visitUnknown(e, p); + } + + /** * {@inheritDoc} * * @implSpec This implementation dispatches to the visit method
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitorRecord.java Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,132 @@ +/* + * 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 source + * versions with records. + * + * 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 + * @since amber + */ +@SupportedSourceVersion(RELEASE_13) +public class ElementKindVisitorRecord<R, P> extends ElementKindVisitor9<R, P> { + /** + * Constructor for concrete subclasses; uses {@code null} for the + * default value. + */ + protected ElementKindVisitorRecord() { + super(null); + } + + /** + * Constructor for concrete subclasses; uses the argument for the + * default value. + * + * @param defaultValue the value to assign to {@link #DEFAULT_VALUE} + */ + protected ElementKindVisitorRecord(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} + * + * @since amber + */ + @Override + public R visitTypeAsRecord(TypeElement 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} + * + * @since amber + */ + @Override + public R visitVariableAsStateComponent(VariableElement e, P p) { + return defaultAction(e, p); + } +}
--- a/src/java.compiler/share/classes/javax/lang/model/util/Elements.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/java.compiler/share/classes/javax/lang/model/util/Elements.java Fri Jun 14 11:12:54 2019 +0200 @@ -614,4 +614,48 @@ * @since 1.8 */ boolean isFunctionalInterface(TypeElement type); + + /** + * 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 executable element for the setter associated with the given variable element. + * + * @implSpec The default implementation of this method returns + * {@code null}. + * + * @param variableElement the field for which the setter is to be found. + * @return the field's setter; otherwise {@code null} if there is no getter. + * @since amber + */ + default ExecutableElement setterFor(VariableElement variableElement) { + return null; + } + + /** + * Return {@code true} if the type element is sealed, {@code + * false} otherwise. This method takes into account non-sealing of + * types. + * + * @implSpec The default implementation of this method returns + * {@code false}. + * + * @param type the type element being examined + * @return {@code true} if the type element is sealed, {@code false} otherwise + * @since amber + */ + default boolean isSealed(TypeElement type) { + return false; + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/AccessorTree.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTree.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTreeVisitor.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/source/util/SimpleDocTreeVisitor.java Fri Jun 14 11:12:54 2019 +0200 @@ -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); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Accessors.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java Fri Jun 14 11:12:54 2019 +0200 @@ -334,20 +334,38 @@ */ public static final long MATCH_BINDING_TO_OUTER = 1L<<60; + /** + * Flag to indicate sealed class/interface declaration. + */ + public static final long SEALED = 1L<<61; + + /** + * Flag to indicate that the class/interface has explicitly being annotated as not sealed. + */ + public static final long NON_FINAL = 1L<<62; + + /** + * 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<<63; + /** Modifier masks. */ public static final int - AccessFlags = PUBLIC | PROTECTED | PRIVATE, - LocalClassFlags = FINAL | ABSTRACT | STRICTFP | ENUM | SYNTHETIC, - MemberClassFlags = LocalClassFlags | INTERFACE | AccessFlags, - ClassFlags = LocalClassFlags | INTERFACE | PUBLIC | ANNOTATION, - InterfaceVarFlags = FINAL | STATIC | PUBLIC, - VarFlags = AccessFlags | FINAL | STATIC | - VOLATILE | TRANSIENT | ENUM, - ConstructorFlags = AccessFlags, - InterfaceMethodFlags = ABSTRACT | PUBLIC, - MethodFlags = AccessFlags | ABSTRACT | STATIC | NATIVE | - SYNCHRONIZED | FINAL | STRICTFP; + 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 | + VOLATILE | TRANSIENT | ENUM, + ConstructorFlags = AccessFlags, + InterfaceMethodFlags = ABSTRACT | PUBLIC, + MethodFlags = AccessFlags | ABSTRACT | STATIC | NATIVE | + SYNCHRONIZED | FINAL | STRICTFP; public static final long ExtendedStandardFlags = (long)StandardFlags | DEFAULT, ModifierFlags = ((long)StandardFlags & ~INTERFACE) | DEFAULT, @@ -366,6 +384,7 @@ if (0 != (flags & PRIVATE)) modifiers.add(Modifier.PRIVATE); if (0 != (flags & ABSTRACT)) modifiers.add(Modifier.ABSTRACT); if (0 != (flags & STATIC)) modifiers.add(Modifier.STATIC); + if (0 != (flags & SEALED)) modifiers.add(Modifier.SEALED); if (0 != (flags & FINAL)) modifiers.add(Modifier.FINAL); if (0 != (flags & TRANSIENT)) modifiers.add(Modifier.TRANSIENT); if (0 != (flags & VOLATILE)) modifiers.add(Modifier.VOLATILE); @@ -451,7 +470,9 @@ 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), + SEALED(Flags.SEALED), + RECORD(Flags.RECORD); Flag(long flag) { this.value = flag;
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java Fri Jun 14 11:12:54 2019 +0200 @@ -199,7 +199,9 @@ 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), + SEALED(JDK14, Fragments.FeatureSealedTypes, DiagKind.PLURAL), + RECORDS(JDK14); enum DiagKind { NORMAL,
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java Fri Jun 14 11:12:54 2019 +0200 @@ -370,6 +370,10 @@ return (flags_field & DEPRECATED) != 0; } + public boolean isRecord() { + return (flags_field & RECORD) != 0; + } + public boolean hasDeprecatedAnnotation() { return (flags_field & DEPRECATED_ANNOTATION) != 0; } @@ -402,6 +406,10 @@ return (flags() & INTERFACE) != 0; } + public boolean isAbstract() { + return (flags() & ABSTRACT) != 0; + } + public boolean isPrivate() { return (flags_field & Flags.AccessFlags) == PRIVATE; } @@ -410,6 +418,14 @@ return (flags() & ENUM) != 0; } + public boolean isSealed() { + return (flags_field & SEALED) != 0; + } + + 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 @@ -1429,6 +1445,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 +1458,13 @@ return Flags.asModifierSet(flags & ~DEFAULT); } + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public java.util.List<VariableElement> getStateComponents() { + apiComplete(); + // Inital implementation + return javax.lang.model.util.ElementFilter.stateComponentsIn(getEnclosedElements()); + } + @DefinedBy(Api.LANGUAGE_MODEL) public NestingKind getNestingKind() { apiComplete(); @@ -1530,6 +1555,11 @@ Assert.check(!annotationTypeMetadata.isMetadataForAnnotationType()); this.annotationTypeMetadata = a; } + + @DefinedBy(Api.LANGUAGE_MODEL) + public List<Type> getPermittedSubtypes() { + return ((ClassType)type).permitted; + } } @@ -1552,6 +1582,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 +1628,23 @@ 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); + if (!accessors.isEmpty()) { + for (Pair<Accessors.Kind, MethodSymbol> accessorPair : accessors) { + if (accessorPair.fst == Accessors.Kind.GET) { + ((MethodType)accessorPair.snd.type).restype = erasure_field; + } else { + // set accessors are not yet generated + } + } + } + } + return erasure_field; + } + @DefinedBy(Api.LANGUAGE_MODEL) public ElementKind getKind() { long flags = flags(); @@ -1606,6 +1655,8 @@ return ElementKind.PARAMETER; } else if ((flags & ENUM) != 0) { return ElementKind.ENUM_CONSTANT; + } else if ((flags & RECORD) != 0) { + return ElementKind.STATE_COMPONENT; } else if (owner.kind == TYP || owner.kind == ERR) { return ElementKind.FIELD; } else if (isResourceVariable()) { @@ -1780,6 +1831,10 @@ ClassFile.CONSTANT_InterfaceMethodref : ClassFile.CONSTANT_Methodref; } + public boolean isDynamic() { + return false; + } + public boolean isHandle() { return false; } @@ -2159,6 +2214,7 @@ public static class MethodHandleSymbol extends MethodSymbol implements LoadableConstant { private Symbol refSym; + // in case the simbol is a variable private boolean getter; public MethodHandleSymbol(Symbol msym) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java Fri Jun 14 11:12:54 2019 +0200 @@ -161,6 +161,7 @@ /** Predefined types. */ public final Type objectType; + public final Type objectMethodBuildersType; public final Type objectsType; public final Type classType; public final Type classLoaderType; @@ -214,6 +215,8 @@ public final Type documentedType; public final Type elementTypeType; public final Type functionalInterfaceType; + public final Type extractorType; + public final Type typeDescriptorType; /** The symbol representing the length field of an array. */ @@ -508,6 +511,7 @@ // Enter predefined classes. All are assumed to be in the java.base module. objectType = enterClass("java.lang.Object"); + objectMethodBuildersType = enterClass("java.lang.invoke.ObjectMethodBuilders"); objectsType = enterClass("java.util.Objects"); classType = enterClass("java.lang.Class"); stringType = enterClass("java.lang.String"); @@ -570,6 +574,8 @@ lambdaMetafactory = enterClass("java.lang.invoke.LambdaMetafactory"); stringConcatFactory = enterClass("java.lang.invoke.StringConcatFactory"); functionalInterfaceType = enterClass("java.lang.FunctionalInterface"); + extractorType = enterClass("java.lang.runtime.Extractor"); + typeDescriptorType = enterClass("java.lang.invoke.TypeDescriptor"); synthesizeEmptyInterfaceIfMissing(autoCloseableType); synthesizeEmptyInterfaceIfMissing(cloneableType);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Type.java Fri Jun 14 11:12:54 2019 +0200 @@ -974,6 +974,12 @@ */ public List<Type> all_interfaces_field; + /** The classes, or interfaces, permitted to extend this class, or interface + */ + public List<Type> permitted; + + public boolean isPermittedExplicit = false; + public ClassType(Type outer, List<Type> typarams, TypeSymbol tsym) { this(outer, typarams, tsym, TypeMetadata.EMPTY); } @@ -986,6 +992,7 @@ this.allparams_field = null; this.supertype_field = null; this.interfaces_field = null; + this.permitted = List.nil(); } public int poolTag() {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java Fri Jun 14 11:12:54 2019 +0200 @@ -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()) { @@ -5177,7 +5190,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/Attr.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java Fri Jun 14 11:12:54 2019 +0200 @@ -169,6 +169,7 @@ allowStaticInterfaceMethods = Feature.STATIC_INTERFACE_METHODS.allowedInSource(source); sourceName = source.name; useBeforeDeclarationWarning = options.isSet("useBeforeDeclarationWarning"); + dontErrorIfSealedExtended = options.isSet("dontErrorIfSealedExtended"); statInfo = new ResultInfo(KindSelector.NIL, Type.noType); varAssignmentInfo = new ResultInfo(KindSelector.ASG, Type.noType); @@ -206,6 +207,13 @@ boolean useBeforeDeclarationWarning; /** + * Temporary switch, false by default but if set, allows generating classes that can extend a sealed class + * even if not listed as a permitted subtype. This allows testing the VM runtime. Should be removed before sealed types + * gets integrated + */ + boolean dontErrorIfSealedExtended; + + /** * Switch: name of source level; used for error reporting. */ String sourceName; @@ -877,6 +885,7 @@ * @param interfaceExpected true if only an interface is expected here. */ Type attribBase(JCTree tree, + ClassSymbol subType, Env<AttrContext> env, boolean classExpected, boolean interfaceExpected, @@ -884,9 +893,10 @@ Type t = tree.type != null ? tree.type : attribType(tree, env); - return checkBase(t, tree, env, classExpected, interfaceExpected, checkExtensible); + return checkBase(t, subType, tree, env, classExpected, interfaceExpected, checkExtensible); } Type checkBase(Type t, + ClassSymbol subType, JCTree tree, Env<AttrContext> env, boolean classExpected, @@ -1094,12 +1104,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)) { @@ -4736,7 +4745,7 @@ Set<Type> boundSet = new HashSet<>(); if (bounds.nonEmpty()) { // accept class or interface or typevar as first bound. - bounds.head.type = checkBase(bounds.head.type, bounds.head, env, false, false, false); + bounds.head.type = checkBase(bounds.head.type, syms.unknownSymbol, bounds.head, env, false, false, false); boundSet.add(types.erasure(bounds.head.type)); if (bounds.head.type.isErroneous()) { return bounds.head.type; @@ -4752,7 +4761,7 @@ // if first bound was a class or interface, accept only interfaces // as further bounds. for (JCExpression bound : bounds.tail) { - bound.type = checkBase(bound.type, bound, env, false, true, false); + bound.type = checkBase(bound.type, syms.unknownSymbol, bound, env, false, true, false); if (bound.type.isErroneous()) { bounds = List.of(bound); } @@ -5028,6 +5037,44 @@ chk.validate(tree.implementing, env); } + Type st = types.supertype(c.type); + boolean anyParentIsSealed = false; + ListBuffer<Pair<ClassType, JCExpression>> potentiallySealedParents = new ListBuffer<>(); + if (st != Type.noType && (st.tsym.isSealed())) { + potentiallySealedParents.add(new Pair<>((ClassType)st, tree.extending)); + anyParentIsSealed = true; + } + + if (tree.implementing != null) { + for (JCExpression expr : tree.implementing) { + if (expr.type.tsym.isSealed()) { + potentiallySealedParents.add(new Pair<>((ClassType)expr.type, expr)); + anyParentIsSealed = true; + } + } + } + + for (Pair<ClassType, JCExpression> sealedParentPair: potentiallySealedParents) { + if (!sealedParentPair.fst.permitted.map(t -> t.tsym).contains(c.type.tsym)) { + boolean areNestmates = sealedParentPair.fst.tsym.outermostClass() == tree.sym.outermostClass(); + boolean isSealed = sealedParentPair.fst.tsym.isSealed(); + if (areNestmates) { + if (sealedParentPair.fst.tsym.isSealed() && !((ClassType)sealedParentPair.fst.tsym.type).isPermittedExplicit) { + sealedParentPair.fst.permitted = sealedParentPair.fst.permitted.prepend(tree.sym.type); + } else if (!dontErrorIfSealedExtended) { + log.error(sealedParentPair.snd, Errors.CantInheritFromSealed(sealedParentPair.fst.tsym)); + } + } else if (!dontErrorIfSealedExtended) { + log.error(sealedParentPair.snd, Errors.CantInheritFromSealed(sealedParentPair.fst.tsym)); + } + } + } + + if (anyParentIsSealed) { + // once we have the non-final keyword this will change + c.flags_field |= (c.flags_field & ABSTRACT) != 0 ? SEALED : FINAL; + } + c.markAbstractIfNeeded(types); // If this is a non-abstract class, check that it has no abstract
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/AttrContext.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/AttrContext.java Fri Jun 14 11:12:54 2019 +0200 @@ -25,6 +25,7 @@ package com.sun.tools.javac.comp; +import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.util.*; import com.sun.tools.javac.code.*; @@ -117,6 +118,8 @@ */ JCTree preferredTreeForDiagnostics; + MethodSymbol recordImplicitConstructor; + /** Duplicate this context, replacing scope field and copying all others. */ AttrContext dup(WriteableScope scope) { @@ -138,6 +141,7 @@ info.isNewClass = isNewClass; info.preferredTreeForDiagnostics = preferredTreeForDiagnostics; info.visitingServiceImplementation = visitingServiceImplementation; + info.recordImplicitConstructor = recordImplicitConstructor; return info; }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java Fri Jun 14 11:12:54 2019 +0200 @@ -1169,17 +1169,22 @@ break; case TYP: if (sym.isLocal()) { - mask = LocalClassFlags; + mask = (flags & RECORD) != 0 ? LocalRecordFlags : LocalClassFlags; 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 ? MemberRecordClassFlags : MemberClassFlags; if (sym.owner.owner.kind == PCK || (sym.owner.flags_field & STATIC) != 0) mask |= STATIC; - else if ((flags & ENUM) != 0) + else if ((flags & ENUM) != 0) { log.error(pos, Errors.EnumsMustBeStatic); + } // Nested interfaces and enums are always STATIC (Spec ???) if ((flags & (INTERFACE | ENUM)) != 0 ) implicit = STATIC; } else { @@ -1193,6 +1198,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; @@ -3110,8 +3119,9 @@ 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) || + (s.owner.kind == TYP && s.owner.isRecord())) { return true; } } else if (target == names.CONSTRUCTOR) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java Fri Jun 14 11:12:54 2019 +0200 @@ -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.*; @@ -2142,17 +2143,21 @@ if (isInitialConstructor) { boolean isSynthesized = (tree.sym.flags() & GENERATEDCONSTR) != 0; - for (int i = firstadr; i < nextadr; i++) { - JCVariableDecl vardecl = vardecls[i]; - VarSymbol var = vardecl.sym; - if (var.owner == classDef.sym) { - // choose the diagnostic position based on whether - // the ctor is default(synthesized) or not - if (isSynthesized) { - checkInit(TreeInfo.diagnosticPositionFor(var, vardecl), - var, Errors.VarNotInitializedInDefaultConstructor(var)); - } else { - checkInit(TreeInfo.diagEndPos(tree.body), var); + boolean isRecord = (tree.sym.owner.flags() & Flags.RECORD) != 0; + // skip record as they are generated by the compiler and guaranteed to be correct + if (!isRecord || !isSynthesized) { + for (int i = firstadr; i < nextadr; i++) { + JCVariableDecl vardecl = vardecls[i]; + VarSymbol var = vardecl.sym; + if (var.owner == classDef.sym) { + // choose the diagnostic position based on whether + // the ctor is default(synthesized) or not + if (isSynthesized) { + checkInit(TreeInfo.diagnosticPositionFor(var, vardecl), + var, Errors.VarNotInitializedInDefaultConstructor(var)); + } else { + checkInit(TreeInfo.diagEndPos(tree.body), var); + } } } }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java Fri Jun 14 11:12:54 2019 +0200 @@ -25,16 +25,21 @@ package com.sun.tools.javac.comp; +import sun.invoke.util.BytecodeName; + import java.util.*; import java.util.Map.Entry; import java.util.function.Function; import java.util.stream.Stream; +import java.util.stream.Collectors; 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.*; @@ -57,10 +62,6 @@ import static com.sun.tools.javac.code.TypeTag.*; import static com.sun.tools.javac.code.Kinds.Kind.*; import static com.sun.tools.javac.jvm.ByteCodes.*; -import com.sun.tools.javac.tree.JCTree.JCBreak; -import com.sun.tools.javac.tree.JCTree.JCCase; -import com.sun.tools.javac.tree.JCTree.JCExpression; -import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import static com.sun.tools.javac.tree.JCTree.JCOperatorExpression.OperandPos.LEFT; import com.sun.tools.javac.tree.JCTree.GenericSwitch; import com.sun.tools.javac.tree.JCTree.GenericSwitch.SwitchKind; @@ -803,6 +804,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. @@ -2189,6 +2205,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; @@ -2258,6 +2278,37 @@ 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(); + } + /** Translate an enum class. */ private void visitEnumDef(JCClassDecl tree) { make_at(tree.pos()); @@ -2413,6 +2464,226 @@ 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), + recordExtractor(tree, getterMethHandlesForExtractor), + recordReadResolve(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.objectMethodBuildersType, 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()); + } + } + + JCTree recordExtractor(JCClassDecl tree, MethodHandleSymbol[] getterMethHandles) { + make_at(tree.pos()); + List<Type> fieldTypes = TreeInfo.types(TreeInfo.recordFields(tree)); + String argsTypeSig = '(' + argsTypeSig(fieldTypes) + ')'; + String extractorStr = BytecodeName.toBytecodeName("$pattern$" + tree.sym.name + "$" + argsTypeSig); + Name extractorName = names.fromString(extractorStr); + // public Extractor extractorName () { return ???; } + MethodType extractorMT = new MethodType(List.nil(), syms.extractorType, List.nil(), syms.methodClass); + MethodSymbol extractorSym = new MethodSymbol( + Flags.PUBLIC | Flags.RECORD | Flags.STATIC, + extractorName, extractorMT, tree.sym); + tree.sym.members().enter(extractorSym); + + Name bootstrapName = names.makeLazyExtractor; + LoadableConstant[] staticArgsValues = new LoadableConstant[1 + getterMethHandles.length]; + /** this method descriptor should have the same arguments as the record constructor and its + * return type should be the same as the type of the record + */ + MethodType mt = new MethodType(fieldTypes, tree.type, List.nil(), syms.methodClass); + staticArgsValues[0] = mt; + int index = 1; + for (MethodHandleSymbol mho : getterMethHandles) { + staticArgsValues[index] = mho; + index++; + } + + List<Type> staticArgTypes = List.of(syms.methodTypeType, + new ArrayType(syms.methodHandleType, syms.arrayClass)); + JCFieldAccess qualifier = makeIndyQualifier(syms.extractorType, tree, extractorSym, + List.of(syms.methodHandleLookupType, + syms.stringType, + syms.methodTypeType).appendList(staticArgTypes), + staticArgsValues, bootstrapName, bootstrapName, true); + + JCMethodInvocation proxyCall = make.Apply(List.nil(), qualifier, List.nil()); + proxyCall.type = qualifier.type; + return make.MethodDef(extractorSym, make.Block(0, List.of(make.Return(proxyCall)))); + } + + JCTree recordReadResolve(JCClassDecl tree) { + make_at(tree.pos()); + Symbol msym = findMethodOrFailSilently( + tree.pos(), + attrEnv, + tree.sym.type, + names.readResolve, + List.nil(), + List.nil()); + if (!msym.kind.isResolutionError() && (msym.flags() & RECORD) != 0) { + List<JCExpression> args = TreeInfo.recordFields(tree).map(vd -> make.Ident(vd)); + return make.MethodDef((MethodSymbol)msym, make.Block(0, List.of(make.Return(makeNewClass(tree.sym.type, args))))); + } 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
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MemberEnter.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MemberEnter.java Fri Jun 14 11:12:54 2019 +0200 @@ -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); }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java Fri Jun 14 11:12:54 2019 +0200 @@ -1928,6 +1928,7 @@ List<Type> argtypes, List<Type> typeargtypes, boolean allowBoxing, boolean useVarargs) { Symbol bestSoFar = methodNotFound; + Env<AttrContext> env1 = env; boolean staticOnly = false; while (env1.outer != null) { @@ -2664,7 +2665,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 +2679,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 +4794,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 +4802,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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java Fri Jun 14 11:12:54 2019 +0200 @@ -54,7 +54,10 @@ import static com.sun.tools.javac.code.Kinds.Kind.*; import static com.sun.tools.javac.code.TypeTag.CLASS; import static com.sun.tools.javac.code.TypeTag.ERROR; +import static com.sun.tools.javac.code.TypeTag.NONE; + import com.sun.tools.javac.resources.CompilerProperties.Fragments; + import static com.sun.tools.javac.tree.JCTree.Tag.*; import com.sun.tools.javac.util.Dependencies.CompletionCause; @@ -677,11 +680,11 @@ if (tree.extending != null) { extending = clearTypeParams(tree.extending); - supertype = attr.attribBase(extending, baseEnv, true, false, true); + supertype = attr.attribBase(extending, sym, baseEnv, true, false, true); } else { extending = null; supertype = ((tree.mods.flags & Flags.ENUM) != 0) - ? attr.attribBase(enumBase(tree.pos, sym), baseEnv, + ? attr.attribBase(enumBase(tree.pos, sym), sym, baseEnv, true, false, false) : (sym.fullname == names.java_lang_Object) ? Type.noType @@ -695,7 +698,7 @@ List<JCExpression> interfaceTrees = tree.implementing; for (JCExpression iface : interfaceTrees) { iface = clearTypeParams(iface); - Type it = attr.attribBase(iface, baseEnv, false, true, true); + Type it = attr.attribBase(iface, sym, baseEnv, false, true, true); if (it.hasTag(CLASS)) { interfaces.append(it); if (all_interfaces != null) all_interfaces.append(it); @@ -706,6 +709,15 @@ } } + // Determine permits. + ListBuffer<Type> permittedSubtypes = new ListBuffer<>(); + List<JCExpression> permittedTrees = tree.permitting; + for (JCExpression permitted : permittedTrees) { + permitted = clearTypeParams(permitted); + Type pt = attr.attribBase(permitted, sym, baseEnv, false, false, false); + permittedSubtypes.append(pt); + } + if ((sym.flags_field & ANNOTATION) != 0) { ct.interfaces_field = List.of(syms.annotationType); ct.all_interfaces_field = ct.interfaces_field; @@ -714,6 +726,9 @@ ct.all_interfaces_field = (all_interfaces == null) ? ct.interfaces_field : all_interfaces.toList(); } + + ct.permitted = permittedSubtypes.toList(); + ct.isPermittedExplicit = !permittedSubtypes.isEmpty(); } //where: protected JCExpression clearTypeParams(JCExpression superType) { @@ -801,7 +816,7 @@ private final class HeaderPhase extends AbstractHeaderPhase { public HeaderPhase() { - super(CompletionCause.HEADER_PHASE, new MembersPhase()); + super(CompletionCause.HEADER_PHASE, new RecordPhase()); } @Override @@ -851,12 +866,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 +893,96 @@ 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) { + memberEnter.memberEnter(TreeInfo.recordFields(tree), env); + } + } + } + + /** 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).map(vd -> vd.sym)); + } + 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) { + RecordConstructorHelper helper = new RecordConstructorHelper( + sym, + TreeInfo.recordFields(tree).map(vd -> vd.sym)); + JCMethodDecl methDecl = (JCMethodDecl)def; + 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); + } + ListBuffer<JCStatement> initializations = new ListBuffer<>(); + List<Name> inits = helper.inits(); + InitializationFinder initFinder = new InitializationFinder(inits); + initFinder.scan(methDecl.body.stats); + List<Name> found = initFinder.found.toList(); + inits = inits.diff(found); + if (!inits.isEmpty()) { + for (Name initName : inits) { + initializations.add(make.Exec(make.Assign(make.Select(make.Ident(names._this), + initName), make.Ident(initName)))); + } + methDecl.body.stats = methDecl.body.stats.appendList(initializations.toList()); + } + } + } } } - 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 +1004,7 @@ } } - finishClass(tree, env); + finishClass(tree, env, defaultConstructorGenerated); if (allowTypeAnnos) { typeAnnotations.organizeTypeAnnotationsSignatures(env, (JCClassDecl)env.tree); @@ -953,15 +1012,46 @@ } } + class InitializationFinder extends TreeScanner { + List<Name> fieldNames; + ListBuffer<Name> found = new ListBuffer<>(); + + public InitializationFinder(List<Name> fieldNames) { + this.fieldNames = fieldNames; + } + + @Override + public void visitAssign(JCAssign tree) { + super.visitAssign(tree); + if (tree.lhs.hasTag(SELECT)) { + JCFieldAccess select = (JCFieldAccess)tree.lhs; + if ((select.selected.hasTag(IDENT)) && + ((JCIdent)select.selected).name == names._this) { + if (fieldNames.contains(select.name)) { + found.add(select.name); + } + } + } + } + } + /** 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); + List<JCTree> defsToEnter = (tree.sym.flags_field & RECORD) != 0 ? + tree.defs.diff(List.convert(JCTree.class, TreeInfo.recordFields(tree))) : tree.defs; + memberEnter.memberEnter(defsToEnter, env); + if ((tree.mods.flags & RECORD) != 0) { + if ((tree.mods.flags & (RECORD | ABSTRACT)) == RECORD) { + addRecordMembersIfNeeded(tree, env, defaultConstructorGenerated); + } + addAccessorsIfNeeded(tree, env); + } if (tree.sym.isAnnotationType()) { Assert.check(tree.sym.isCompleted()); @@ -969,6 +1059,39 @@ } } + /** 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); + Symbol 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), + 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); + tree.sym.accessors = tree.sym.accessors.prepend(new Pair<>(accessor.fst, getter.sym)); + } else if (implSym != null && (implSym.flags() & Flags.PUBLIC) == 0) { + log.error(TreeInfo.declarationFor(implSym, env.enclClass), Errors.MethodMustBePublic(implSym.name)); + } + } + } + /** Add the implicit members for an enum type * to the symbol table. */ @@ -1003,136 +1126,270 @@ memberEnter.memberEnter(valueOf, env); } + /** Add the implicit members for a record + * to the symbol table. + */ + private void addRecordMembersIfNeeded(JCClassDecl tree, Env<AttrContext> env, boolean defaultConstructorGenerated) { + 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); + } + + if (attr.isSerializable(tree.sym.type)) { + if (lookupMethod(tree.sym, names.readResolve, List.nil()) == null && + lookupMethod(tree.sym, names.readObject, List.nil()) == null) { + // private Object readResolve() { return ???; } + JCMethodDecl readResolve = make. + MethodDef(make.Modifiers(Flags.PRIVATE | Flags.RECORD | Flags.FINAL | Flags.MANDATED), + names.readResolve, + make.Type(syms.objectType), + List.nil(), + List.nil(), + List.nil(), // thrown + null, + null); + memberEnter.memberEnter(readResolve, env); + } + } + } + + } + + private Symbol 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 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)) { - // constructors of true enums are private - flags = (flags & ~AccessFlags) | PRIVATE | GENERATEDCONSTR; - } else - flags |= (c.flags() & AccessFlags) | GENERATEDCONSTR; - if (c.name.isEmpty()) { - flags |= ANONCONSTR; + interface DefaultConstructorHelper { + Type constructorType(); + MethodSymbol constructorSymbol(); + Type enclosingType(); + TypeSymbol owner(); + List<Name> superArgs(); + List<Name> inits(); + } + + 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; } - if (based) { - flags |= ANONCONSTR_BASED; + + @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 = PRIVATE | GENERATEDCONSTR; + } else { + flags = (owner().flags() & AccessFlags) | GENERATEDCONSTR; + } + 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(); + } + + @Override + public List<Name> inits() { + return List.nil(); + } } - 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; + 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; + } + + @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; - } else { - meth = make.Ident(names._super); + class RecordConstructorHelper extends BasicConstructorHelper { + + List<VarSymbol> recordFields; + + RecordConstructorHelper(TypeSymbol owner, List<VarSymbol> recordFields) { + super(owner); + this.recordFields = recordFields; + } + + @Override + public Type constructorType() { + if (constructorType == null) { + List<Type> argtypes = recordFields.map(v -> v.type); + constructorType = new MethodType(argtypes, syms.voidType, List.nil(), syms.methodClass); + } + return constructorType; + } + + @Override + public MethodSymbol constructorSymbol() { + MethodSymbol csym = super.constructorSymbol(); + ListBuffer<VarSymbol> params = new ListBuffer<>(); + for (VarSymbol p : recordFields) { + params.add(new VarSymbol(MANDATED | PARAMETER, p.name, p.type, csym)); + } + csym.params = params.toList(); + csym.flags_field |= RECORD | PUBLIC; + return csym; } - List<JCExpression> typeargs = typarams.nonEmpty() ? make.Types(typarams) : null; - return make.Exec(make.Apply(typeargs, meth, make.Idents(params))); + + @Override + public List<Name> inits() { + return recordFields.map(v -> v.name); + } + } + + 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 = initType.getTypeArguments().nonEmpty() ? + make.Types(initType.getTypeArguments()) : null; + JCStatement superCall = make.Exec(make.Apply(typeargs, meth, helper.superArgs().map(make::Ident))); + stats.add(superCall); + } + helper.inits().forEach((initName) -> { + stats.add(make.Exec(make.Assign(make.Select(make.Ident(names._this), initName), make.Ident(initName)))); + }); + JCTree result = make.MethodDef(initSym, make.Block(0, stats.toList())); + return result; } /**
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java Fri Jun 14 11:12:54 2019 +0200 @@ -105,6 +105,14 @@ */ boolean allowModules; + /** Switch: allow sealed + */ + boolean allowSealedTypes; + + /** Switch: allow records + */ + boolean allowRecords; + /** Lint option: warn about classfile issues */ boolean lintClassfile; @@ -264,6 +272,8 @@ Source source = Source.instance(context); preview = Preview.instance(context); allowModules = Feature.MODULES.allowedInSource(source); + allowSealedTypes = Feature.SEALED.allowedInSource(source); + allowRecords = Feature.RECORDS.allowedInSource(source); saveParameterNames = options.isSet(PARAMETERS); @@ -1181,6 +1191,38 @@ } } }, + + new AttributeReader(names.PermittedSubtypes, V57, CLASS_ATTRIBUTE) { + @Override + protected boolean accepts(AttributeKind kind) { + return super.accepts(kind) && allowSealedTypes; + } + protected void read(Symbol sym, int attrLen) { + if (sym.kind == TYP) { + ClassType sealed = (ClassType)sym.type; + ListBuffer<Type> subtypes = new ListBuffer<>(); + int numberOfPermittedSubtypes = nextChar(); + for (int i = 0; i < numberOfPermittedSubtypes; i++) { + Type ct = poolReader.getClass(nextChar()).erasure(types); + subtypes.add(ct); + } + sealed.permitted = subtypes.toList(); + } + } + }, + + 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) @@ -2441,6 +2483,10 @@ for (int i = 0; i < methodCount; i++) skipMember(); readClassAttrs(c); + if (ct.permitted != null && !ct.permitted.isEmpty()) { + c.flags_field |= SEALED; + } + // reset and read rest of classinfo bp = startbp; int n = nextChar();
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java Fri Jun 14 11:12:54 2019 +0200 @@ -406,6 +406,7 @@ 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 +428,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 +447,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 +832,34 @@ endAttr(alenIdx); } + int writeRecordAttribute(ClassSymbol csym) { + int alenIdx = writeAttr(names.Record); + Scope s = csym.members(); + List<VarSymbol> vars = List.nil(); + int numParams = 0; + for (Symbol sym : s.getSymbols(NON_RECURSIVE)) { + if (sym.kind == VAR && sym.isRecord()) { + vars = vars.prepend((VarSymbol)sym); + numParams++; + } + } + databuf.appendChar(numParams); + for (VarSymbol v: vars) { + databuf.appendChar(poolWriter.putName(v.name)); + databuf.appendChar(adjustFlags(v.flags())); + // descriptor + databuf.appendChar(poolWriter.putSignature(v)); + // signature + databuf.appendChar(poolWriter.putSignature(v)); + } + int acountIdx = beginAttrs(); + int acount = 0; + acount += writeParameterAttrs(vars); + endAttrs(acountIdx, acount); + endAttr(alenIdx); + return 1; + } + /** * Write NestMembers attribute (if needed) */ @@ -881,6 +910,22 @@ } } + /** Write "PermittedSubtypes" attribute. + */ + int writePermittedSubtypesIfNeeded(ClassSymbol csym) { + ClassType ct = (ClassType)csym.type; + if (ct.permitted.nonEmpty()) { + int alenIdx = writeAttr(names.PermittedSubtypes); + databuf.appendChar(ct.permitted.size()); + for (Type t : ct.permitted) { + databuf.appendChar(poolWriter.putClass((ClassSymbol)t.tsym)); + } + endAttr(alenIdx); + return 1; + } + return 0; + } + /** Write "bootstrapMethods" attribute. */ void writeBootstrapMethods() { @@ -966,7 +1011,7 @@ } acount += writeMemberAttrs(m); if (!m.isLambdaMethod()) - acount += writeParameterAttrs(m); + acount += writeParameterAttrs(m.params); endAttrs(acountIdx, acount); } @@ -1491,6 +1536,12 @@ flags = ACC_MODULE; } else { flags = adjustFlags(c.flags() & ~DEFAULT); + if (c.isSealed()) { + flags &= ~SEALED; + if (((ClassType)c.type).permitted.isEmpty()) { + flags |= FINAL; + } + } if ((flags & PROTECTED) != 0) flags |= PUBLIC; flags = flags & ClassFlags & ~STRICTFP; if ((flags & INTERFACE) == 0) flags |= ACC_SUPER; @@ -1600,6 +1651,14 @@ } } + if (c.isRecord()) { + acount += writeRecordAttribute(c); + } + + if (target.hasSealedTypes()) { + acount += writePermittedSubtypesIfNeeded(c); + } + if (!poolWriter.bootstrapMethods.isEmpty()) { writeBootstrapMethods(); acount++;
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/PoolConstant.java Fri Jun 14 11:12:54 2019 +0200 @@ -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/jvm/Target.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Target.java Fri Jun 14 11:12:54 2019 +0200 @@ -172,4 +172,9 @@ return compareTo(JDK1_11) >= 0; } + /** Does the target VM support sealed types + */ + public boolean hasSealedTypes() { + return compareTo(JDK1_12) >= 0; + } }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java Fri Jun 14 11:12:54 2019 +0200 @@ -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.RECORD | 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,23 @@ public void newRound() { resultCache.clear(); } + + @Override + public ExecutableElement getterFor(VariableElement variableElement) { + return accessorFor(Kind.GET, variableElement); + } + + @Override + public ExecutableElement setterFor(VariableElement variableElement) { + return accessorFor(Kind.SET, 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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java Fri Jun 14 11:12:54 2019 +0200 @@ -101,7 +101,7 @@ private Preview preview; /** The name table. */ - private Names names; + protected Names names; /** End position mappings container */ protected final AbstractEndPosTable endPosTable; @@ -2422,7 +2422,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)); } @@ -2557,6 +2557,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: @@ -2568,30 +2569,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); @@ -2638,7 +2639,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)) { @@ -3084,6 +3092,23 @@ case MONKEYS_AT : flag = Flags.ANNOTATION; break; case DEFAULT : checkSourceLevel(Feature.DEFAULT_METHODS); flag = Flags.DEFAULT; break; case ERROR : flag = 0; nextToken(); break; + case IDENTIFIER : { + if (token.name() == names.non && peekToken(0, TokenKind.SUB, TokenKind.FINAL)) { + Token tokenSub = S.token(1); + Token tokenFinal = S.token(2); + if (token.endPos == tokenSub.pos && tokenSub.endPos == tokenFinal.pos) { + flag = Flags.NON_FINAL; + nextToken(); + nextToken(); + break; + } + } + if (isSealedClassDeclaration()) { + flag = Flags.SEALED; + break; + } + break loop; + } default: break loop; } if ((flags & flag) != 0) log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.RepeatedModifier); @@ -3328,6 +3353,10 @@ return false; } + boolean isRestrictedRecordTypeName(Name name) { + return Feature.RECORDS.allowedInSource(source) && name == names.record; + } + /** VariableDeclaratorId = Ident BracketsOpt */ JCVariableDecl variableDeclaratorId(JCModifiers mods, JCExpression type) { @@ -3649,7 +3678,7 @@ nextToken(); return toP(F.at(pos).Skip()); } else { - return classOrInterfaceOrEnumDeclaration(modifiersOpt(mods), docComment); + return classOrRecordOrInterfaceOrEnumDeclaration(modifiersOpt(mods), docComment); } } @@ -3658,9 +3687,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) { @@ -3678,7 +3709,7 @@ if (parseModuleInfo) { erroneousTree = syntaxError(pos, errs, Errors.ExpectedModuleOrOpen); } else { - erroneousTree = syntaxError(pos, errs, Errors.Expected3(CLASS, INTERFACE, ENUM)); + erroneousTree = syntaxError(pos, errs, Errors.Expected4(CLASS, INTERFACE, ENUM, RECORD)); } return toP(F.Exec(erroneousTree)); } @@ -3706,9 +3737,74 @@ nextToken(); implementing = typeList(); } - List<JCTree> defs = classOrInterfaceBody(name, false); + List<JCExpression> permitting = List.nil(); + if (token.kind == IDENTIFIER && token.name() == names.permits) { + if ((mods.flags & Flags.SEALED) == 0) { + log.error(token.pos, Errors.PermitsInNoSealedClass); + } + nextToken(); + permitting = typeList(); + } + List<JCTree> defs = classInterfaceOrRecordBody(name, false, false); JCClassDecl result = toP(F.at(pos).ClassDef( - mods, name, typarams, extending, implementing, defs)); + mods, name, typarams, extending, implementing, permitting, 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 = + headerFields((mods.flags & Flags.ABSTRACT) != 0); + + List<JCExpression> implementing = List.nil(); + if (token.kind == IMPLEMENTS) { + nextToken(); + implementing = typeList(); + } + List<JCTree> defs = List.nil(); + if (token.kind == LBRACE) { + defs = classInterfaceOrRecordBody(name, false, true); + } else { + accept(SEMI); + } + 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.name, param.vartype, null)); + } + methDef.params = tmpParams.toList(); + } + } + } + for (int i = fields.size() - 1; i >= 0; i--) { + defs = defs.prepend(fields.get(i)); + } + JCClassDecl result = toP(F.at(pos).ClassDef(mods, name, typarams, null, implementing, defs)); attach(result, dc); return result; } @@ -3719,9 +3815,48 @@ 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(boolean abstractRecord) { + 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(abstractRecord)); + while (token.kind == COMMA) { + nextToken(); + fields.add(headerField(abstractRecord)); + } + accept(RPAREN); + } else { + accept(LPAREN); + } + return fields.toList(); + } + + JCVariableDecl headerField(boolean abstractRecord) { + JCModifiers mods = modifiersOpt(); + if (mods.flags != 0) { + log.error(mods.pos, Errors.RecordCantDeclareFieldModifiers); + } + mods.flags |= Flags.RECORD | Flags.FINAL; + mods.flags |= abstractRecord ? Flags.PROTECTED : 0; + JCExpression type = parseType(); + int pos = token.pos; + Name id = ident(); + 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 @@ -3740,9 +3875,23 @@ nextToken(); extending = typeList(); } - List<JCTree> defs = classOrInterfaceBody(name, true); + List<JCExpression> permitting = List.nil(); + if (token.kind == IDENTIFIER && token.name() == names.permits) { + if ((mods.flags & Flags.SEALED) == 0) { + log.error(token.pos, Errors.PermitsInNoSealedClass); + } + nextToken(); + permitting = typeList(); + } + List<JCTree> defs; + if (token.kind == LBRACE) { + defs = classInterfaceOrRecordBody(name, true, false); + } else { + accept(SEMI); + defs = List.nil(); + } JCClassDecl result = toP(F.at(pos).ClassDef( - mods, name, typarams, null, extending, defs)); + mods, name, typarams, null, extending, permitting, defs)); attach(result, dc); return result; } @@ -3795,8 +3944,8 @@ if (token.kind == SEMI) { nextToken(); while (token.kind != RBRACE && token.kind != EOF) { - defs.appendList(classOrInterfaceBodyDeclaration(enumName, - false)); + defs.appendList(classOrInterfaceOrRecordBodyDeclaration(enumName, + false, false)); if (token.pos <= endPosTable.errorEndPos) { // error recovery skip(false, true, true, false); @@ -3827,7 +3976,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) @@ -3857,7 +4006,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 @@ -3867,7 +4016,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); @@ -3906,7 +4055,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(); @@ -3915,9 +4064,10 @@ int pos = token.pos; JCModifiers mods = modifiersOpt(); 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()) { @@ -3954,28 +4104,34 @@ // 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); 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()) { - List<JCTree> defs = - variableDeclaratorsRest(pos, mods, type, name, isInterface, dc, - new ListBuffer<JCTree>(), false).toList(); - accept(SEMI); - storeEnd(defs.last(), S.prevToken().endPos); - return defs; + if (!isRecord || (isRecord && (mods.flags & Flags.STATIC) != 0)) { + List<JCTree> defs = + variableDeclaratorsRest(pos, mods, type, name, isInterface, dc, + new ListBuffer<JCTree>(), false).toList(); + accept(SEMI); + 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; @@ -3995,6 +4151,19 @@ } } + boolean isRecordToken() { + return token.kind == IDENTIFIER && token.name() == names.record; + } + + boolean isSealedClassDeclaration() { + Token next = S.token(1); + return token.kind == IDENTIFIER && token.name() == names.sealed && + (peekToken(TokenKind.CLASS) || + peekToken(TokenKind.INTERFACE) || + peekToken(TokenKind.ABSTRACT) || + next.kind == IDENTIFIER && next.name() == names.record); + } + /** MethodDeclaratorRest = * FormalParameters BracketsOpt [THROWS TypeList] ( MethodBody | [DEFAULT AnnotationValue] ";") * VoidMethodDeclaratorRest = @@ -4008,6 +4177,7 @@ Name name, List<JCTypeParameter> typarams, boolean isInterface, boolean isVoid, + boolean isRecord, Comment dc) { if (isInterface) { if ((mods.flags & Flags.STATIC) != 0) { @@ -4021,12 +4191,15 @@ try { this.receiverParam = null; // Parsing formalParameters sets the receiverParam, if present - List<JCVariableDecl> params = formalParameters(); - if (!isVoid) type = bracketsOpt(type); + List<JCVariableDecl> params = List.nil(); List<JCExpression> thrown = List.nil(); - if (token.kind == THROWS) { - nextToken(); - thrown = qualidentList(true); + if (!isRecord || name != names.init || token.kind == LPAREN) { + params = formalParameters(); + if (!isVoid) type = bracketsOpt(type); + if (token.kind == THROWS) { + nextToken(); + thrown = qualidentList(true); + } } JCBlock body = null; JCExpression defaultValue; @@ -4049,7 +4222,6 @@ } } } - JCMethodDecl result = toP(F.at(pos).MethodDef(mods, name, type, typarams, receiverParam, params, thrown,
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Tokens.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Tokens.java Fri Jun 14 11:12:54 2019 +0200 @@ -84,8 +84,8 @@ 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) - key[tokenName[t.ordinal()].getIndex()] = t; + if (t.name != null && !t.reserved()) + key[tokenName[t.ordinal()].getIndex()] = t; } } @@ -226,6 +226,10 @@ GTGTEQ(">>="), GTGTGTEQ(">>>="), MONKEYS_AT("@"), + VAR("var", Tag.RESERVED), + RECORD("record", Tag.RESERVED), + SEALED("sealed", Tag.RESERVED), + PERMITS("permits", Tag.RESERVED), CUSTOM; public final String name; @@ -276,6 +280,10 @@ } } + public boolean reserved() { + return tag == Tag.RESERVED; + } + public String getKind() { return "Token"; } @@ -315,7 +323,8 @@ DEFAULT, NAMED, STRING, - NUMERIC + NUMERIC, + RESERVED; } /** The token kind */
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java Fri Jun 14 11:12:54 2019 +0200 @@ -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; @@ -216,6 +218,16 @@ printFormalTypeParameters(e, false); + if (kind == RECORD) { + // Print out state components + writer.print("("); + writer.print(e.getStateComponents() + .stream() + .map(stateDes -> stateDes.asType().toString() + " " + stateDes.getSimpleName()) + .collect(Collectors.joining(", "))); + writer.print(")"); + } + // Print superclass information if informative if (kind == CLASS) { TypeMirror supertype = e.getSuperclass(); @@ -228,6 +240,7 @@ } printInterfaces(e); + printPermittedSubtypes(e); } writer.println(" {"); indentation++; @@ -255,7 +268,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 +467,10 @@ modifiers.remove(Modifier.ABSTRACT); break; + case RECORD: + modifiers.remove(Modifier.FINAL); + break; + case METHOD: case FIELD: Element enclosingElement = e.getEnclosingElement(); @@ -572,6 +595,17 @@ } } + private void printPermittedSubtypes(TypeElement e) { + List<? extends TypeMirror> subtypes = e.getPermittedSubtypes(); + if (!subtypes.isEmpty()) { // could remove this check with more complicated joining call + writer.print(" permits "); + writer.print(subtypes + .stream() + .map(subtype -> subtype.toString()) + .collect(Collectors.joining(", "))); + } + } + private void printThrows(ExecutableElement e) { List<? extends TypeMirror> thrownTypes = e.getThrownTypes(); final int size = thrownTypes.size();
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties Fri Jun 14 11:12:54 2019 +0200 @@ -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 @@ -2891,6 +2895,9 @@ compiler.misc.feature.var.syntax.in.implicit.lambda=\ var syntax in implicit lambdas +compiler.misc.feature.sealed.types=\ + sealed types + compiler.warn.underscore.as.identifier=\ as of release 9, ''_'' is a keyword, and may not be used as an identifier @@ -3396,6 +3403,70 @@ compiler.err.switch.mixing.case.types=\ different case kinds used in the switch +### +# errors related to sealed classes + +compiler.err.permits.in.no.sealed.class=\ + permits clause can only appear in a sealed class + +# 0: symbol +compiler.err.cant.inherit.from.sealed=\ + cannot inherit from sealed class: {0} + +### +# 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 10, ''{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 + ############################################ # messages previouly at javac.properties
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java Fri Jun 14 11:12:54 2019 +0200 @@ -767,6 +767,8 @@ public JCExpression extending; /** the interfaces implemented by this class */ public List<JCExpression> implementing; + /** the subclasses allowed to extend this class, if sealed */ + public List<JCExpression> permitting; /** all variables and methods defined in this class */ public List<JCTree> defs; /** the symbol */ @@ -776,6 +778,7 @@ List<JCTypeParameter> typarams, JCExpression extending, List<JCExpression> implementing, + List<JCExpression> permitting, List<JCTree> defs, ClassSymbol sym) { @@ -784,6 +787,7 @@ this.typarams = typarams; this.extending = extending; this.implementing = implementing; + this.permitting = permitting; this.defs = defs; this.sym = sym; } @@ -939,23 +943,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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java Fri Jun 14 11:12:54 2019 +0200 @@ -98,6 +98,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 +209,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(...)? */
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java Fri Jun 14 11:12:54 2019 +0200 @@ -161,11 +161,23 @@ List<JCExpression> implementing, List<JCTree> defs) { + return ClassDef(mods, name, typarams, extending, implementing, List.nil(), defs); + } + + public JCClassDecl ClassDef(JCModifiers mods, + Name name, + List<JCTypeParameter> typarams, + JCExpression extending, + List<JCExpression> implementing, + List<JCExpression> permitting, + List<JCTree> defs) + { JCClassDecl tree = new JCClassDecl(mods, name, typarams, extending, implementing, + permitting, defs, null); tree.pos = pos; @@ -210,7 +222,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; } @@ -851,7 +867,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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Dependencies.java Fri Jun 14 11:12:54 2019 +0200 @@ -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 Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java Fri Jun 14 11:12:54 2019 +0200 @@ -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; @@ -155,6 +161,7 @@ public final Name Synthetic; public final Name Value; public final Name Varargs; + public final Name PermittedSubtypes; // members of java.lang.annotation.ElementType public final Name ANNOTATION_TYPE; @@ -191,6 +198,19 @@ public final Name makeConcat; public final Name makeConcatWithConstants; + // record related + // members of java.lang.invoke.ObjectMethodBuilders + public final Name bootstrap; + + public final Name record; + public final Name where; + public final Name non; + public final Name makeLazyExtractor; + + // sealed types + public final Name permits; + public final Name sealed; + public final Name.Table table; public Names(Context context) { @@ -220,6 +240,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 +255,7 @@ deserializeLambda = fromString("$deserializeLambda$"); desiredAssertionStatus = fromString("desiredAssertionStatus"); equals = fromString("equals"); + oldEquals = fromString("oldEquals"); error = fromString("<error>"); finalize = fromString("finalize"); forRemoval = fromString("forRemoval"); @@ -250,6 +273,8 @@ value = fromString("value"); valueOf = fromString("valueOf"); values = fromString("values"); + readResolve = fromString("readResolve"); + readObject = fromString("readObject"); dollarThis = fromString("$this"); // class names @@ -292,6 +317,7 @@ ModuleResolution = fromString("ModuleResolution"); NestHost = fromString("NestHost"); NestMembers = fromString("NestMembers"); + Record = fromString("Record"); RuntimeInvisibleAnnotations = fromString("RuntimeInvisibleAnnotations"); RuntimeInvisibleParameterAnnotations = fromString("RuntimeInvisibleParameterAnnotations"); RuntimeInvisibleTypeAnnotations = fromString("RuntimeInvisibleTypeAnnotations"); @@ -306,6 +332,7 @@ Synthetic = fromString("Synthetic"); Value = fromString("Value"); Varargs = fromString("Varargs"); + PermittedSubtypes = fromString("PermittedSubtypes"); // members of java.lang.annotation.ElementType ANNOTATION_TYPE = fromString("ANNOTATION_TYPE"); @@ -340,6 +367,16 @@ // string concat makeConcat = fromString("makeConcat"); makeConcatWithConstants = fromString("makeConcatWithConstants"); + + bootstrap = fromString("bootstrap"); + record = fromString("record"); + where = fromString("where"); + non = fromString("non"); + makeLazyExtractor = fromString("makeLazyExtractor"); + + // sealed types + permits = fromString("permits"); + sealed = fromString("sealed"); } protected Name.Table createTable(Options options) {
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java Fri Jun 14 11:12:54 2019 +0200 @@ -28,12 +28,16 @@ import java.util.List; import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.ModuleElement; +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 com.sun.source.doctree.DocTree; +import com.sun.source.doctree.DocTree.Kind; import com.sun.source.doctree.IndexTree; import com.sun.source.doctree.SystemPropertyTree; import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; @@ -271,6 +275,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(", ");
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java Fri Jun 14 11:12:54 2019 +0200 @@ -26,6 +26,7 @@ package jdk.javadoc.internal.doclets.toolkit; import java.io.*; +import java.lang.ref.*; import java.util.*; import javax.lang.model.element.Element;
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.java Fri Jun 14 11:12:54 2019 +0200 @@ -223,6 +223,17 @@ utils.removeCommentHelper(element); } + public void setAccessorCommentTree(Element element, List<DocTree> fullBody, + List<DocTree> blockTags, Utils utils) { + DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, blockTags); + TreePath pathToEncl = utils.docTrees.getPath(element.getEnclosingElement()); + dcTreesMap.put(element, new DocCommentDuo(pathToEncl, docTree)); + // There maybe an entry with the original comments usually null, + // therefore remove that entry if it exists, and allow a new one + // to be reestablished. + utils.removeCommentHelper(element); + } + /** * A simplistic container to transport a TreePath, DocCommentTree pair. * Here is why we need this:
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties Fri Jun 14 11:12:54 2019 +0200 @@ -65,6 +65,8 @@ doclet.PropertySetter=Sets the value of the property doclet.PropertyGetterWithName=Gets the value of the property {0}. doclet.PropertySetterWithName=Sets the value of the property {0}. +doclet.FieldGetterWithName=Gets the value of the field {0}. +doclet.FieldSetterWithName=Sets the value of the field {0}. doclet.Default=Default: doclet.Parameters=Parameters: doclet.TypeParameters=Type Parameters: @@ -76,6 +78,8 @@ 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:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/AccessorTaglet.java Fri Jun 14 11:12:54 2019 +0200 @@ -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/TagletManager.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletManager.java Fri Jun 14 11:12:54 2019 +0200 @@ -642,6 +642,8 @@ addStandardTaglet(new CodeTaglet()); addStandardTaglet(new IndexTaglet()); addStandardTaglet(new SummaryTaglet()); + addStandardTaglet(new AccessorTaglet(GETTER)); + addStandardTaglet(new AccessorTaglet(SETTER)); addStandardTaglet(new SystemPropertyTaglet()); // Keep track of the names of standard tags for error checking purposes.
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletWriter.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/TagletWriter.java Fri Jun 14 11:12:54 2019 +0200 @@ -25,6 +25,7 @@ package jdk.javadoc.internal.doclets.toolkit.taglets; +import java.util.EnumSet; import java.util.List; import javax.lang.model.element.Element; @@ -152,6 +153,15 @@ protected abstract Content seeTagOutput(Element holder, List<? extends DocTree> seeTags); /** + * Return the accessor tag output. + * + * @param holder + * @param tags the accessor tags + * @return the output of the accessor tag. + */ + protected abstract Content accessorTagOutput(Element holder, List<? extends DocTree> tags); + + /** * Return the output for a simple tag. * * @param element
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java Fri Jun 14 11:12:54 2019 +0200 @@ -213,6 +213,17 @@ return null; } + public ExecutableElement findAccessorFor(VariableElement field, DocTree.Kind kind) { + switch (kind) { + case GETTER: + return elementUtils.getterFor(field); + case SETTER: + return elementUtils.setterFor(field); + default: + throw new IllegalStateException("Cannot get here!"); + } + } + /** * Test whether a class is a subclass of another class. *
--- a/src/jdk.jdeps/share/classes/com/sun/tools/classfile/Attribute.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.jdeps/share/classes/com/sun/tools/classfile/Attribute.java Fri Jun 14 11:12:54 2019 +0200 @@ -60,12 +60,14 @@ public static final String ModuleTarget = "ModuleTarget"; public static final String NestHost = "NestHost"; public static final String NestMembers = "NestMembers"; + public static final String Record = "Record"; public static final String RuntimeVisibleAnnotations = "RuntimeVisibleAnnotations"; public static final String RuntimeInvisibleAnnotations = "RuntimeInvisibleAnnotations"; public static final String RuntimeVisibleParameterAnnotations = "RuntimeVisibleParameterAnnotations"; public static final String RuntimeInvisibleParameterAnnotations = "RuntimeInvisibleParameterAnnotations"; public static final String RuntimeVisibleTypeAnnotations = "RuntimeVisibleTypeAnnotations"; public static final String RuntimeInvisibleTypeAnnotations = "RuntimeInvisibleTypeAnnotations"; + public static final String PermittedSubtypes = "PermittedSubtypes"; public static final String Signature = "Signature"; public static final String SourceDebugExtension = "SourceDebugExtension"; public static final String SourceFile = "SourceFile"; @@ -134,12 +136,14 @@ standardAttributes.put(ModuleTarget, ModuleTarget_attribute.class); standardAttributes.put(NestHost, NestHost_attribute.class); standardAttributes.put(NestMembers, NestMembers_attribute.class); + standardAttributes.put(Record, Record_attribute.class); standardAttributes.put(RuntimeInvisibleAnnotations, RuntimeInvisibleAnnotations_attribute.class); standardAttributes.put(RuntimeInvisibleParameterAnnotations, RuntimeInvisibleParameterAnnotations_attribute.class); standardAttributes.put(RuntimeVisibleAnnotations, RuntimeVisibleAnnotations_attribute.class); standardAttributes.put(RuntimeVisibleParameterAnnotations, RuntimeVisibleParameterAnnotations_attribute.class); standardAttributes.put(RuntimeVisibleTypeAnnotations, RuntimeVisibleTypeAnnotations_attribute.class); standardAttributes.put(RuntimeInvisibleTypeAnnotations, RuntimeInvisibleTypeAnnotations_attribute.class); + standardAttributes.put(PermittedSubtypes, PermittedSubtypes_attribute.class); standardAttributes.put(Signature, Signature_attribute.class); standardAttributes.put(SourceDebugExtension, SourceDebugExtension_attribute.class); standardAttributes.put(SourceFile, SourceFile_attribute.class); @@ -199,12 +203,14 @@ R visitModuleTarget(ModuleTarget_attribute attr, P p); R visitNestHost(NestHost_attribute attr, P p); R visitNestMembers(NestMembers_attribute attr, P p); + R visitRecord(Record_attribute attr, P p); R visitRuntimeVisibleAnnotations(RuntimeVisibleAnnotations_attribute attr, P p); R visitRuntimeInvisibleAnnotations(RuntimeInvisibleAnnotations_attribute attr, P p); R visitRuntimeVisibleParameterAnnotations(RuntimeVisibleParameterAnnotations_attribute attr, P p); R visitRuntimeInvisibleParameterAnnotations(RuntimeInvisibleParameterAnnotations_attribute attr, P p); R visitRuntimeVisibleTypeAnnotations(RuntimeVisibleTypeAnnotations_attribute attr, P p); R visitRuntimeInvisibleTypeAnnotations(RuntimeInvisibleTypeAnnotations_attribute attr, P p); + R visitPermittedSubtypes(PermittedSubtypes_attribute attr, P p); R visitSignature(Signature_attribute attr, P p); R visitSourceDebugExtension(SourceDebugExtension_attribute attr, P p); R visitSourceFile(SourceFile_attribute attr, P p);
--- a/src/jdk.jdeps/share/classes/com/sun/tools/classfile/ClassWriter.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.jdeps/share/classes/com/sun/tools/classfile/ClassWriter.java Fri Jun 14 11:12:54 2019 +0200 @@ -642,6 +642,22 @@ } @Override + public Void visitRecord(Record_attribute attr, ClassOutputStream out) { + out.writeShort(attr.num_params); + for (Record_attribute.Param_data e: attr.params) + writeParamData(e, out); + new AttributeWriter().write(attr.attributes, out); + return null; + } + + protected void writeParamData(Record_attribute.Param_data pd, ClassOutputStream out) { + out.writeShort(pd.param_name_index); + out.writeShort(pd.param_flags); + out.writeShort(pd.param_descriptor); + out.writeShort(pd.param_signature); + } + + @Override public Void visitRuntimeInvisibleAnnotations(RuntimeInvisibleAnnotations_attribute attr, ClassOutputStream out) { annotationWriter.write(attr.annotations, out); return null; @@ -682,6 +698,16 @@ } @Override + public Void visitPermittedSubtypes(PermittedSubtypes_attribute attr, ClassOutputStream out) { + int n = attr.subtypes.length; + out.writeShort(n); + for (int i = 0 ; i < n ; i++) { + out.writeShort(attr.subtypes[i]); + } + return null; + } + + @Override public Void visitSignature(Signature_attribute attr, ClassOutputStream out) { out.writeShort(attr.signature_index); return null;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jdeps/share/classes/com/sun/tools/classfile/PermittedSubtypes_attribute.java Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,65 @@ +/* + * 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.classfile; + +import java.io.IOException; +import java.util.stream.IntStream; + +import com.sun.tools.classfile.ConstantPool.CONSTANT_Class_info; + +public class PermittedSubtypes_attribute extends Attribute { + + public int[] subtypes; + + PermittedSubtypes_attribute(ClassReader cr, int name_index, int length) throws IOException { + super(name_index, length); + int number_of_classes = cr.readUnsignedShort(); + subtypes = new int[number_of_classes]; + for (int i = 0; i < number_of_classes; i++) + subtypes[i] = cr.readUnsignedShort(); + } + + public PermittedSubtypes_attribute(int name_index, int[] subtypes) { + super(name_index, 2); + this.subtypes = subtypes; + } + + public CONSTANT_Class_info[] getSubtypes(ConstantPool constant_pool) throws ConstantPoolException { + return IntStream.of(subtypes) + .mapToObj(i -> { + try { + return constant_pool.getClassInfo(i); + } catch (ConstantPoolException ex) { + throw new AssertionError(ex); + } + }).toArray(CONSTANT_Class_info[]::new); + } + + @Override + public <R, D> R accept(Visitor<R, D> visitor, D data) { + return visitor.visitPermittedSubtypes(this, data); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jdeps/share/classes/com/sun/tools/classfile/Record_attribute.java Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018, 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.classfile; + +import java.io.IOException; +import com.sun.tools.classfile.Attribute.Visitor; + +/** + * <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> + */ +public class Record_attribute extends Attribute { + Record_attribute(ClassReader cr, int name_index, int length) throws IOException { + super(name_index, length); + num_params = cr.readUnsignedShort(); + params = new Param_data[num_params]; + for (int i = 0; i < num_params; i++) { + params[i] = new Param_data(cr); + } + attributes = new Attributes(cr); + } + + @Override + public <R, D> R accept(Visitor<R, D> visitor, D data) { + return visitor.visitRecord(this, data); + } + + public final int num_params; + public final Param_data[] params; + public final Attributes attributes; + + public static class Param_data { + Param_data(ClassReader cr) throws IOException { + param_name_index = cr.readUnsignedShort(); + param_flags = cr.readUnsignedShort(); + param_descriptor = cr.readUnsignedShort(); + param_signature = cr.readUnsignedShort(); + } + + public String getName(ConstantPool constant_pool) throws ConstantPoolException { + return constant_pool.getUTF8Value(param_name_index); + } + + public final int param_name_index; + public final int param_flags; + public final int param_descriptor; + public final int param_signature; + } +}
--- a/src/jdk.jdeps/share/classes/com/sun/tools/javap/AttributeWriter.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.jdeps/share/classes/com/sun/tools/javap/AttributeWriter.java Fri Jun 14 11:12:54 2019 +0200 @@ -25,6 +25,8 @@ package com.sun.tools.javap; +import java.util.Collection; + import com.sun.tools.classfile.AccessFlags; import com.sun.tools.classfile.AnnotationDefault_attribute; import com.sun.tools.classfile.Attribute; @@ -40,6 +42,8 @@ import com.sun.tools.classfile.ConstantValue_attribute; import com.sun.tools.classfile.DefaultAttribute; import com.sun.tools.classfile.Deprecated_attribute; +import com.sun.tools.classfile.Descriptor; +import com.sun.tools.classfile.Descriptor.InvalidDescriptor; import com.sun.tools.classfile.EnclosingMethod_attribute; import com.sun.tools.classfile.Exceptions_attribute; import com.sun.tools.classfile.InnerClasses_attribute; @@ -56,6 +60,8 @@ import com.sun.tools.classfile.ModuleTarget_attribute; import com.sun.tools.classfile.NestHost_attribute; import com.sun.tools.classfile.NestMembers_attribute; +import com.sun.tools.classfile.Record_attribute; +import com.sun.tools.classfile.Record_attribute.Param_data; import com.sun.tools.classfile.RuntimeInvisibleAnnotations_attribute; import com.sun.tools.classfile.RuntimeInvisibleParameterAnnotations_attribute; import com.sun.tools.classfile.RuntimeInvisibleTypeAnnotations_attribute; @@ -63,6 +69,8 @@ import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute; import com.sun.tools.classfile.RuntimeVisibleParameterAnnotations_attribute; import com.sun.tools.classfile.RuntimeVisibleTypeAnnotations_attribute; +import com.sun.tools.classfile.PermittedSubtypes_attribute; +import com.sun.tools.classfile.Signature; import com.sun.tools.classfile.Signature_attribute; import com.sun.tools.classfile.SourceDebugExtension_attribute; import com.sun.tools.classfile.SourceFile_attribute; @@ -70,12 +78,17 @@ import com.sun.tools.classfile.StackMapTable_attribute; import com.sun.tools.classfile.StackMap_attribute; import com.sun.tools.classfile.Synthetic_attribute; +import com.sun.tools.classfile.Type; import static com.sun.tools.classfile.AccessFlags.*; import com.sun.tools.javac.util.Assert; import com.sun.tools.javac.util.StringUtils; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + /* * A writer for writing Attributes as text. * @@ -714,6 +727,72 @@ } @Override + public Void visitRecord(Record_attribute attr, Void p) { + println("Record:"); + indent(+1); + for (int i = 0; i < attr.num_params; i++) { + writeParamData(attr.params[i]); + } + write(attr, attr.attributes, constant_pool); + indent(-1); + return null; + } + + void writeParamData(Param_data pd) { + AccessFlags flags = new AccessFlags(pd.param_flags); + writeModifiers(flags.getFieldModifiers()); + Descriptor descriptor = new Descriptor(pd.param_descriptor); + print(getJavaFieldType(descriptor)); + print(" "); + try { + print(pd.getName(constant_pool)); + } catch (ConstantPoolException cpe) { + // ignore + } + println(";"); + try { + indent(+1); + println("descriptor: " + descriptor.getValue(constant_pool)); + writeList(String.format("flags: (0x%04x) ", flags.flags), flags.getFieldFlags(), "\n"); + Signature signature = new Signature(pd.param_signature); + Type t = signature.getType(constant_pool); + println("signature: " + getJavaName(t.toString())); + indent(-1); + } catch (ConstantPoolException cpe) { + // ignore + } + println(); + } + + void writeList(String prefix, Collection<?> items, String suffix) { + print(prefix); + String sep = ""; + for (Object item: items) { + print(sep); + print(item); + sep = ", "; + } + print(suffix); + } + + String getJavaFieldType(Descriptor d) { + try { + return getJavaName(d.getFieldType(constant_pool)); + } catch (ConstantPoolException e) { + return report(e); + } catch (InvalidDescriptor e) { + return report(e); + } + } + + void writeModifiers(Collection<String> items) { + for (Object item: items) { + print(item); + print(" "); + } + } + + @Override public Void visitRuntimeVisibleAnnotations(RuntimeVisibleAnnotations_attribute attr, Void ignore) { println("RuntimeVisibleAnnotations:"); indent(+1); @@ -794,6 +873,22 @@ } @Override + public Void visitPermittedSubtypes(PermittedSubtypes_attribute attr, Void ignore) { + println("PermittedSubtypes:"); + indent(+1); + try { + CONSTANT_Class_info[] subtypes = attr.getSubtypes(constant_pool); + for (int i = 0; i < subtypes.length; i++) { + println(constantWriter.stringValue(subtypes[i])); + } + indent(-1); + } catch (ConstantPoolException ex) { + throw new AssertionError(ex); + } + return null; + } + + @Override public Void visitSignature(Signature_attribute attr, Void ignore) { print("Signature: #" + attr.signature_index); tab();
--- a/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java Fri Jun 14 11:12:54 2019 +0200 @@ -52,6 +52,8 @@ import java.util.function.Function; import java.util.function.Supplier; +import com.sun.tools.javac.util.Names; + /** * Low level scanner to determine completeness of input. * @author Robert Field @@ -60,6 +62,7 @@ private final ScannerFactory scannerFactory; private final JShell proc; + private final Names names; private static Completeness error() { return Completeness.UNKNOWN; // For breakpointing @@ -81,6 +84,7 @@ Log log = CaLog.createLog(context); context.put(Log.class, log); context.put(Source.class, Source.JDK9); + names = Names.instance(context); scannerFactory = ScannerFactory.instance(context); } @@ -88,6 +92,7 @@ try { Parser parser = new Parser( () -> new Matched(scannerFactory.newScanner(s, false)), + names, worker -> proc.taskFactory.parse(s, worker)); Completeness stat = parser.parseUnit(); int endPos = stat == Completeness.UNKNOWN @@ -161,6 +166,7 @@ private static final int XSTART = 0b1000000000; // Boundary, must be XTERM before private static final int XERRO = 0b10000000000; // Is an error private static final int XBRACESNEEDED = 0b100000000000; // Expect {ANY} LBRACE + private static final int XMODIFIER = 0b1000000000000; // Modifier /** * An extension of the compiler's TokenKind which adds our combined/processed @@ -186,6 +192,9 @@ IDENTIFIER(TokenKind.IDENTIFIER, XEXPR1|XDECL1|XTERM), // UNDERSCORE(TokenKind.UNDERSCORE, XERRO), // _ CLASS(TokenKind.CLASS, XEXPR|XDECL1|XBRACESNEEDED), // class decl (MAPPED: DOTCLASS) + RECORD(TokenKind.RECORD, XEXPR|XDECL1), // record decl (MAPPED: DOTCLASS) + SEALED(TokenKind.SEALED, XEXPR|XDECL1), // sealed class decl (MAPPED: DOTCLASS) + PERMITS(TokenKind.PERMITS, XEXPR|XDECL), // permits classlist MONKEYS_AT(TokenKind.MONKEYS_AT, XEXPR|XDECL1), // @ IMPORT(TokenKind.IMPORT, XDECL1|XSTART), // import -- consider declaration SEMI(TokenKind.SEMI, XSTMT1|XTERM|XSTART), // ; @@ -212,18 +221,19 @@ LONG(TokenKind.LONG, XEXPR1|XDECL1), // long SHORT(TokenKind.SHORT, XEXPR1|XDECL1), // short VOID(TokenKind.VOID, XEXPR1|XDECL1), // void + VAR(TokenKind.VAR, XEXPR1|XDECL1|XTERM), // var // Modifiers keywords - ABSTRACT(TokenKind.ABSTRACT, XDECL1), // abstract - FINAL(TokenKind.FINAL, XDECL1), // final - NATIVE(TokenKind.NATIVE, XDECL1), // native - STATIC(TokenKind.STATIC, XDECL1), // static - STRICTFP(TokenKind.STRICTFP, XDECL1), // strictfp - PRIVATE(TokenKind.PRIVATE, XDECL1), // private - PROTECTED(TokenKind.PROTECTED, XDECL1), // protected - PUBLIC(TokenKind.PUBLIC, XDECL1), // public - TRANSIENT(TokenKind.TRANSIENT, XDECL1), // transient - VOLATILE(TokenKind.VOLATILE, XDECL1), // volatile + ABSTRACT(TokenKind.ABSTRACT, XDECL1 | XMODIFIER), // abstract + FINAL(TokenKind.FINAL, XDECL1 | XMODIFIER), // final + NATIVE(TokenKind.NATIVE, XDECL1 | XMODIFIER), // native + STATIC(TokenKind.STATIC, XDECL1 | XMODIFIER), // static + STRICTFP(TokenKind.STRICTFP, XDECL1 | XMODIFIER), // strictfp + PRIVATE(TokenKind.PRIVATE, XDECL1 | XMODIFIER), // private + PROTECTED(TokenKind.PROTECTED, XDECL1 | XMODIFIER), // protected + PUBLIC(TokenKind.PUBLIC, XDECL1 | XMODIFIER), // public + TRANSIENT(TokenKind.TRANSIENT, XDECL1 | XMODIFIER), // transient + VOLATILE(TokenKind.VOLATILE, XDECL1 | XMODIFIER), // volatile // Declarations and type parameters (thus expressions) EXTENDS(TokenKind.EXTENDS, XEXPR|XDECL), // extends @@ -386,6 +396,10 @@ return (belongs & XBRACESNEEDED) != 0; } + boolean isModifier() { + return (belongs & XMODIFIER) != 0; + } + /** * After construction, check that all compiler TokenKind values have * corresponding TK values. @@ -420,10 +434,13 @@ /** The error message **/ public final String message; + public final Token tok; + private CT(TK tk, Token tok, String msg) { this.kind = tk; this.endPos = tok.endPos; this.message = msg; + this.tok = tok; //throw new InternalError(msg); /* for debugging */ } @@ -431,12 +448,14 @@ this.kind = tk; this.endPos = tok.endPos; this.message = null; + this.tok = tok; } private CT(TK tk, int endPos) { this.kind = tk; this.endPos = endPos; this.message = null; + this.tok = null; } } @@ -565,11 +584,14 @@ private Matched in; private CT token; private Completeness checkResult; + private final Names names; Parser(Supplier<Matched> matchedFactory, + Names names, Function<Worker<ParseTask, Completeness>, Completeness> parseFactory) { this.matchedFactory = matchedFactory; this.parseFactory = parseFactory; + this.names = names; resetInput(); } @@ -652,9 +674,13 @@ public Completeness parseDeclaration() { boolean isImport = token.kind == IMPORT; + boolean isDatum = false; + boolean afterModifiers = false; boolean isBracesNeeded = false; while (token.kind.isDeclaration()) { isBracesNeeded |= token.kind.isBracesNeeded(); + isDatum |= !afterModifiers && token.kind == TK.IDENTIFIER && token.tok.name() == names.record; + afterModifiers |= !token.kind.isModifier(); nextToken(); } switch (token.kind) { @@ -673,12 +699,19 @@ case BRACES: case SEMI: return Completeness.COMPLETE; + case VAR: case IDENTIFIER: return isBracesNeeded ? Completeness.DEFINITELY_INCOMPLETE : Completeness.COMPLETE_WITH_SEMI; case BRACKETS: return Completeness.COMPLETE_WITH_SEMI; + case PARENS: + if (isDatum) { + return Completeness.COMPLETE_WITH_SEMI; + } else { + return Completeness.DEFINITELY_INCOMPLETE; + } case DOTSTAR: if (isImport) { return Completeness.COMPLETE_WITH_SEMI;
--- a/src/jdk.jshell/share/classes/jdk/jshell/ReplParser.java Fri Jun 14 08:37:37 2019 +0200 +++ b/src/jdk.jshell/share/classes/jdk/jshell/ReplParser.java Fri Jun 14 11:12:54 2019 +0200 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -59,6 +59,7 @@ import com.sun.tools.javac.util.Name; import com.sun.tools.javac.util.Position; +import static com.sun.tools.javac.parser.Tokens.TokenKind.IDENTIFIER; /** * This is a subclass of JavacParser which overrides one method with a modified * verson of that method designed to allow parsing of one "snippet" of Java @@ -178,9 +179,10 @@ default: JCModifiers mods = modifiersOpt(pmods); if (token.kind == CLASS + || token.kind == IDENTIFIER && token.name() == names.record || token.kind == INTERFACE || token.kind == ENUM) { - return List.<JCTree>of(classOrInterfaceOrEnumDeclaration(mods, dc)); + return List.<JCTree>of(classOrRecordOrInterfaceOrEnumDeclaration(mods, dc)); } else { int pos = token.pos; List<JCTypeParameter> typarams = typeParametersOpt(); @@ -228,7 +230,7 @@ //mods.flags |= Flags.STATIC; return List.of(methodDeclaratorRest( pos, mods, t, name, typarams, - false, isVoid, dc)); + false, isVoid, false, dc)); } else if (!isVoid && typarams.isEmpty()) { // variable declaration //mods.flags |= Flags.STATIC;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/lang/extractor/ExtractorTest.java Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2018, 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. + */ + +import java.lang.runtime.Extractor; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNotSame; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertSame; +import static org.testng.Assert.assertTrue; + +/** + * @test + * @run testng ExtractorTest + * @summary Smoke tests for java.lang.runtime.Extractor + */ +@Test +public class ExtractorTest { + + enum MatchKind { CARRIER, SELF, FAIL, MATCH } + + static void assertMatch(MatchKind kind, Extractor e, Object target, Object... args) throws Throwable { + int count = e.descriptor().parameterCount(); + Object[] bindings = new Object[count]; + Object carrier = Extractor.adapt(e, Object.class).tryMatch().invoke(target); + if (carrier != null) { + for (int i = 0; i < count; i++) + bindings[i] = e.component(i).invoke(carrier); + } + + if (kind == MatchKind.FAIL) + assertNull(carrier); + else { + if (target != null) + assertNotNull(carrier); + assertEquals(bindings.length, args.length); + for (int i = 0; i < args.length; i++) + assertEquals(bindings[i], args[i]); + + if (kind == MatchKind.SELF) + assertSame(carrier, target); + else if (kind == MatchKind.CARRIER) + assertNotSame(carrier, target); + } + } + + private static class TestClass { + static MethodHandle MH_S, MH_I, MH_L, MH_B, MH_PRED; + static MethodHandle CONSTRUCTOR; + static MethodHandle DIGESTER; + static MethodHandle DIGESTER_PARTIAL; + static MethodType TYPE = MethodType.methodType(TestClass.class, String.class, int.class, long.class, byte.class); + static { + try { + MH_B = MethodHandles.lookup().findGetter(TestClass.class, "b", byte.class); + MH_S = MethodHandles.lookup().findGetter(TestClass.class, "s", String.class); + MH_I = MethodHandles.lookup().findGetter(TestClass.class, "i", int.class); + MH_L = MethodHandles.lookup().findGetter(TestClass.class, "l", long.class); + MH_PRED = MethodHandles.lookup().findVirtual(TestClass.class, "matches", MethodType.methodType(boolean.class)); + CONSTRUCTOR = MethodHandles.lookup().findConstructor(TestClass.class, TYPE.changeReturnType(void.class)); + DIGESTER = MethodHandles.lookup().findVirtual(TestClass.class, "digest", MethodType.methodType(Object.class, MethodHandle.class)); + DIGESTER_PARTIAL = MethodHandles.lookup().findVirtual(TestClass.class, "digestPartial", MethodType.methodType(Object.class, MethodHandle.class)); + } + catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + String s; + int i; + long l; + byte b; + + TestClass(String s, int i, long l, byte b) { + this.s = s; + this.i = i; + this.l = l; + this.b = b; + } + + TestClass copy() { + return new TestClass(s, i, l, b); + } + + boolean matches() { return s != null && s.length() == i; } + + Object digest(MethodHandle target) throws Throwable { + return target.invoke(s, i, l, b); + } + + Object digestPartial(MethodHandle target) throws Throwable { + return matches() ? target.invoke(s, i, l, b) : null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestClass aClass = (TestClass) o; + return i == aClass.i && + l == aClass.l && + b == aClass.b && + Objects.equals(s, aClass.s); + } + + @Override + public int hashCode() { + return Objects.hash(s, i, l, b); + } + } + + private static class TestClass2 { + static MethodHandle MH_X; + static MethodType TYPE = MethodType.methodType(TestClass2.class, Object.class); + static { + try { + MH_X = MethodHandles.lookup().findGetter(TestClass2.class, "x", Object.class); + } + catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + Object x; + + public TestClass2(Object x) { + this.x = x; + } + } + + private static final MethodHandle[] COMPONENTS = {TestClass.MH_S, TestClass.MH_I, TestClass.MH_L, TestClass.MH_B }; + + public void testTotal() throws Throwable { + Extractor e = Extractor.ofTotal(TestClass.class, COMPONENTS); + assertMatch(MatchKind.CARRIER, e, new TestClass("foo", 3, 4L, (byte) 5), + "foo", 3, 4L, (byte) 5); + assertMatch(MatchKind.CARRIER, e, new TestClass(null, 0, 0L, (byte) 0), + null, 0, 0L, (byte) 0); + } + + public void testSelfTotal() throws Throwable { + Extractor e = Extractor.ofSelfTotal(TestClass.class, COMPONENTS); + assertMatch(MatchKind.SELF, e, new TestClass("foo", 3, 4L, (byte) 5), + "foo", 3, 4L, (byte) 5); + assertMatch(MatchKind.SELF, e, new TestClass(null, 0, 0L, (byte) 0), + null, 0, 0L, (byte) 0); + } + + public void testPartial() throws Throwable { + Extractor e = Extractor.ofPartial(TestClass.class, TestClass.MH_PRED, COMPONENTS); + assertMatch(MatchKind.CARRIER, e, new TestClass("foo", 3, 4L, (byte) 5), + "foo", 3, 4L, (byte) 5); + assertMatch(MatchKind.FAIL, e, new TestClass("foo", 2, 4L, (byte) 5)); + assertMatch(MatchKind.FAIL, e, new TestClass(null, 0, 0L, (byte) 0)); + } + + public void testSelfPartial() throws Throwable { + Extractor e = Extractor.ofSelfPartial(TestClass.class, TestClass.MH_PRED, COMPONENTS); + assertMatch(MatchKind.SELF, e, new TestClass("foo", 3, 4L, (byte) 5), + "foo", 3, 4L, (byte) 5); + assertMatch(MatchKind.FAIL, e, new TestClass("foo", 2, 4L, (byte) 5)); + assertMatch(MatchKind.FAIL, e, new TestClass(null, 0, 0L, (byte) 0)); + } + + public void testDigest() throws Throwable { + Extractor e = Extractor.of(TestClass.TYPE, TestClass.DIGESTER); + assertMatch(MatchKind.CARRIER, e, new TestClass("foo", 3, 4L, (byte) 5), + "foo", 3, 4L, (byte) 5); + assertMatch(MatchKind.CARRIER, e, new TestClass("foo", 2, 4L, (byte) 5), + "foo", 2, 4L, (byte) 5); + assertMatch(MatchKind.CARRIER, e, new TestClass(null, 0, 0L, (byte) 0), + null, 0, 0L, (byte) 0); + } + + public void testDigestPartial() throws Throwable { + Extractor e = Extractor.of(TestClass.TYPE, TestClass.DIGESTER_PARTIAL); + assertMatch(MatchKind.CARRIER, e, new TestClass("foo", 3, 4L, (byte) 5), + "foo", 3, 4L, (byte) 5); + assertMatch(MatchKind.FAIL, e, new TestClass("foo", 2, 4L, (byte) 5)); + } + + public void testCompose() throws Throwable { + Extractor e = Extractor.ofTotal(TestClass.class, COMPONENTS); + MethodHandle mh = e.compose(TestClass.CONSTRUCTOR, null); + TestClass target = new TestClass("foo", 3, 4L, (byte) 5); + Object o = mh.invoke(target); + assertTrue(o instanceof TestClass); + assertNotSame(target, o); + assertEquals(target, o); + } + + public void testDropBindings() throws Throwable { + Extractor e = Extractor.ofTotal(TestClass.class, COMPONENTS); + assertMatch(MatchKind.CARRIER, e, new TestClass("foo", 3, 4L, (byte) 5), + "foo", 3, 4L, (byte) 5); + assertMatch(MatchKind.CARRIER, Extractor.dropBindings(e, 0), new TestClass("foo", 3, 4L, (byte) 5), + 3, 4L, (byte) 5); + assertMatch(MatchKind.CARRIER, Extractor.dropBindings(e, 0, 0), new TestClass("foo", 3, 4L, (byte) 5), + 3, 4L, (byte) 5); + assertMatch(MatchKind.CARRIER, Extractor.dropBindings(e, 3), new TestClass("foo", 3, 4L, (byte) 5), + "foo", 3, 4L); + assertMatch(MatchKind.CARRIER, Extractor.dropBindings(e, 0, 1, 2, 3), new TestClass("foo", 3, 4L, (byte) 5)); + } + + public void testAsType() throws Throwable { + assertMatch(MatchKind.SELF, Extractor.ofType(String.class), "Foo", "Foo"); + assertMatch(MatchKind.FAIL, Extractor.ofType(String.class), 3); + assertMatch(MatchKind.FAIL, Extractor.ofType(String.class), null); + + assertMatch(MatchKind.SELF, Extractor.ofType(List.class), List.of(3), List.of(3)); + assertMatch(MatchKind.SELF, Extractor.ofType(List.class), List.of(), List.of()); + assertMatch(MatchKind.SELF, Extractor.ofType(List.class), new ArrayList<>(), List.of()); + } + + public void testAsNullableType() throws Throwable { + assertMatch(MatchKind.SELF, Extractor.ofTypeNullable(String.class), "Foo", "Foo"); + assertMatch(MatchKind.FAIL, Extractor.ofTypeNullable(String.class), 3); + assertMatch(MatchKind.MATCH, Extractor.ofTypeNullable(String.class), null, (Object) null); + } + + public void testConstant() throws Throwable { + assertMatch(MatchKind.MATCH, Extractor.ofConstant(null), null); + assertMatch(MatchKind.FAIL, Extractor.ofConstant(null), "foo"); + assertMatch(MatchKind.MATCH, Extractor.ofConstant("foo"), "foo"); + assertMatch(MatchKind.FAIL, Extractor.ofConstant("foo"), "bar"); + assertMatch(MatchKind.FAIL, Extractor.ofConstant("foo"), 3); + assertMatch(MatchKind.FAIL, Extractor.ofConstant("foo"), null); + assertMatch(MatchKind.MATCH, Extractor.ofConstant(3), 3); + } + + public void testNested() throws Throwable { + Extractor TC2 = Extractor.ofTotal(TestClass2.class, TestClass2.MH_X); + Extractor STRING = Extractor.ofType(String.class); + Extractor OBJECT = Extractor.ofType(Object.class); + + assertMatch(MatchKind.CARRIER, Extractor.dropBindings(Extractor.ofNested(TC2, STRING), 0), new TestClass2("foo"), + "foo"); + assertMatch(MatchKind.CARRIER, Extractor.dropBindings(Extractor.ofNested(TC2, OBJECT), 0), new TestClass2("foo"), + "foo"); + assertMatch(MatchKind.FAIL, Extractor.dropBindings(Extractor.ofNested(TC2, STRING), 0), new TestClass2(List.of(3)), + "foo"); + + assertMatch(MatchKind.CARRIER, Extractor.dropBindings(Extractor.ofNested(TC2, Extractor.ofNested(TC2, STRING)), 0, 1), new TestClass2(new TestClass2("foo")), + "foo"); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/lang/extractor/RecordTest.java Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2018, 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. + */ + +import java.lang.runtime.Extractor; +import java.lang.runtime.SwitchBootstraps; +import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +import org.testng.annotations.Test; + +import static java.lang.invoke.MethodHandleInfo.REF_newInvokeSpecial; +import static org.testng.Assert.assertEquals; + +@Test +/** + * @test + * @run testng RecordTest + * @summary End-to-end test for record patterns + */ +public class RecordTest { + record R(int a, String b, double c); + record RR(R r1, R R2); + + private Extractor recordExtractor(Class<?> recordClass, + Class<?>... paramTypes) throws Throwable { + return Extractor.findExtractor(MethodHandles.lookup(), "_", Extractor.class, + recordClass, MethodType.methodType(void.class, paramTypes), recordClass.getName(), REF_newInvokeSpecial); + } + + public void testRecord() throws Throwable { + R r = new R(1, "two", 3.14d); + Extractor rExtract = recordExtractor(R.class, int.class, String.class, double.class); + + MethodHandle tryExtract = Extractor.extractorTryMatch(MethodHandles.lookup(), "_", MethodHandle.class, rExtract); + MethodHandle a = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 0); + MethodHandle b = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 1); + MethodHandle c = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 2); + + Object o = tryExtract.invoke(r); + assertEquals(1, a.invoke(o)); + assertEquals("two", b.invoke(o)); + assertEquals(3.14d, c.invoke(o)); + } + + public void testFakeNested() throws Throwable { + R r1 = new R(1, "two", 3.14d); + R r2 = new R(2, "four", 6.0d); + RR rr = new RR(r1, r2); + + Extractor rExtract = recordExtractor(R.class, int.class, String.class, double.class); + Extractor rrExtract = recordExtractor(RR.class, R.class, R.class); + + MethodHandle tryExtractR = Extractor.extractorTryMatch(MethodHandles.lookup(), "_", MethodHandle.class, rExtract); + MethodHandle ra = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 0); + MethodHandle rb = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 1); + MethodHandle rc = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rExtract, 2); + + MethodHandle tryExtractRr = Extractor.extractorTryMatch(MethodHandles.lookup(), "_", MethodHandle.class, rrExtract); + MethodHandle r1c = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rrExtract, 0); + MethodHandle r2c = Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, rrExtract, 1); + + Object o = tryExtractRr.invoke(rr); + R o1 = (R) r1c.invoke(o); + R o2 = (R) r2c.invoke(o); + + assertEquals(1, ra.invoke(o1)); + assertEquals("two", rb.invoke(o1)); + assertEquals(3.14d, rc.invoke(o1)); + + assertEquals(2, ra.invoke(o2)); + assertEquals("four", rb.invoke(o2)); + assertEquals(6.0d, rc.invoke(o2)); + } + + public void testNested() throws Throwable { + Extractor rExtract = recordExtractor(R.class, int.class, String.class, double.class); + Extractor rrExtract = recordExtractor(RR.class, R.class, R.class); + + Extractor e = Extractor.ofNested(rrExtract, rExtract); + + R r1 = new R(1, "two", 3.14d); + R r2 = new R(2, "four", 6.0d); + RR rr = new RR(r1, r2); + + Object o = e.tryMatch().invoke(rr); + + assertEquals(e.component(0).invoke(o), new R(1, "two", 3.14d)); + assertEquals(e.component(1).invoke(o), new R(2, "four", 6.0d)); + assertEquals(e.component(2).invoke(o), 1); + assertEquals(e.component(3).invoke(o), "two"); + assertEquals(e.component(4).invoke(o), 3.14d); + + Extractor ee = Extractor.ofNested(rrExtract, rExtract, rExtract); + o = ee.tryMatch().invoke(rr); + + assertEquals(ee.component(0).invoke(o), new R(1, "two", 3.14d)); + assertEquals(ee.component(1).invoke(o), new R(2, "four", 6.0d)); + assertEquals(ee.component(2).invoke(o), 1); + assertEquals(ee.component(3).invoke(o), "two"); + assertEquals(ee.component(4).invoke(o), 3.14d); + assertEquals(ee.component(5).invoke(o), 2); + assertEquals(ee.component(6).invoke(o), "four"); + assertEquals(ee.component(7).invoke(o), 6.0d); + } + + record A(int a); + record B(int a, int b); + record S(String s); + record T(String s, String t); + record U(); + + private Object component(Extractor e, int num, Object carrier) throws Throwable { + return Extractor.extractorComponent(MethodHandles.lookup(), "_", MethodHandle.class, + e, num).invoke(carrier); + } + + public void testRecordSwitch() throws Throwable { + Extractor[] extractors = { + recordExtractor(A.class, int.class), + recordExtractor(B.class, int.class, int.class), + recordExtractor(S.class, String.class), + recordExtractor(T.class, String.class, String.class), + recordExtractor(U.class) + }; + + Object[] exemplars = { + new A(1), + new B(2, 3), + new S("four"), + new T("five", "six"), + new U() + }; + + CallSite cs = SwitchBootstraps.patternSwitch(MethodHandles.lookup(), "_", + MethodType.methodType(SwitchBootstraps.PatternSwitchResult.class, Object.class), + extractors); + MethodHandle mh = cs.dynamicInvoker(); + for (int i = 0; i < exemplars.length; i++) { + Object exemplar = exemplars[i]; + SwitchBootstraps.PatternSwitchResult result = (SwitchBootstraps.PatternSwitchResult) mh.invoke(exemplar); + assertEquals(result.index, i); + switch (result.index) { + case 0: + assertEquals(component(extractors[i], 0, result.carrier), 1); + break; + case 1: + assertEquals(component(extractors[i], 0, result.carrier), 2); + assertEquals(component(extractors[i], 1, result.carrier), 3); + break; + case 2: + assertEquals(component(extractors[i], 0, result.carrier), "four"); + break; + case 3: + assertEquals(component(extractors[i], 0, result.carrier), "five"); + assertEquals(component(extractors[i], 1, result.carrier), "six"); + break; + }; + + result = (SwitchBootstraps.PatternSwitchResult) mh.invoke(null); + assertEquals(result.index, -1); + + result = (SwitchBootstraps.PatternSwitchResult) mh.invoke("foo"); + assertEquals(result.index, 5); + } + } + + record Box(Object o1); + + public void testNestedRecord() throws Throwable { + Extractor boxA = Extractor.ofNested(recordExtractor(Box.class, Object.class), + recordExtractor(A.class, int.class)); + Extractor boxB = Extractor.ofNested(recordExtractor(Box.class, Object.class), + recordExtractor(B.class, int.class, int.class)); + + CallSite cs = SwitchBootstraps.patternSwitch(MethodHandles.lookup(), "_", + MethodType.methodType(SwitchBootstraps.PatternSwitchResult.class, Object.class), + boxA, boxB); + MethodHandle mh = cs.dynamicInvoker(); + + assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box(new A(1)))).index, 0); + assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box(new B(2, 3)))).index, 1); + assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box("foo"))).index, 2); + assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(new Box(null))).index, 2); + assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke("foo")).index, 2); + assertEquals(((SwitchBootstraps.PatternSwitchResult) mh.invoke(null)).index, -1); + } + + record RString(String i) { } + record RObject(Object i) { } + record Rint(int i) { } + + + public void testNestedWithConstant() throws Throwable { + Extractor rb = recordExtractor(Box.class, Object.class); + Extractor rs = recordExtractor(RString.class, String.class); + Extractor ro = recordExtractor(RObject.class, Object.class); + Extractor ri = recordExtractor(Rint.class, int.class); + Extractor cs = Extractor.ofConstant("foo"); + Extractor cn = Extractor.ofConstant(null); + Extractor ci = Extractor.ofConstant(3); + + ExtractorTest.assertMatch(ExtractorTest.MatchKind.MATCH, Extractor.ofNested(rs, cs), new RString("foo"), "foo"); + ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL, Extractor.ofNested(rs, cs), new RString("bar")); + ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL, Extractor.ofNested(rs, cs), new RString(null)); + + ExtractorTest.assertMatch(ExtractorTest.MatchKind.MATCH, Extractor.ofNested(ro, cs), new RObject("foo"), "foo"); + ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL, Extractor.ofNested(ro, cs), new RObject("bar")); + ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL, Extractor.ofNested(ro, cs), new RObject(3)); + ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL, Extractor.ofNested(ro, cs), new RObject(null)); + + ExtractorTest.assertMatch(ExtractorTest.MatchKind.MATCH, Extractor.ofNested(ri, ci), new Rint(3), 3); + ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL, Extractor.ofNested(ri, ci), new Rint(2)); + + ExtractorTest.assertMatch(ExtractorTest.MatchKind.MATCH, + Extractor.ofNested(rb, Extractor.ofNested(rs, cs)), + new Box(new RString("foo")), new RString("foo"), "foo"); + ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL, + Extractor.ofNested(rb, Extractor.ofNested(rs, cs)), + new Box(new RString("bar"))); + ExtractorTest.assertMatch(ExtractorTest.MatchKind.FAIL, + Extractor.ofNested(rb, Extractor.ofNested(rs, cs)), + new Box("foo")); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/java/lang/extractor/SwitchBootstrapsTest.java Fri Jun 14 11:12:54 2019 +0200 @@ -0,0 +1,466 @@ +/* + * Copyright (c) 2012, 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. + */ + +import java.io.Serializable; +import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.runtime.SwitchBootstraps; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import jdk.test.lib.RandomFactory; + +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; + +/** + * @test + * @key randomness + * @library /test/lib + * @build jdk.test.lib.RandomFactory + * @run testng SwitchBootstrapsTest + */ +@Test +public class SwitchBootstrapsTest { + private final static Set<Class<?>> BOOLEAN_TYPES = Set.of(boolean.class, Boolean.class); + private final static Set<Class<?>> ALL_INT_TYPES = Set.of(int.class, short.class, byte.class, char.class, + Integer.class, Short.class, Byte.class, Character.class); + private final static Set<Class<?>> SIGNED_NON_BYTE_TYPES = Set.of(int.class, Integer.class, short.class, Short.class); + private final static Set<Class<?>> CHAR_TYPES = Set.of(char.class, Character.class); + private final static Set<Class<?>> BYTE_TYPES = Set.of(byte.class, Byte.class); + private final static Set<Class<?>> SIGNED_TYPES + = Set.of(int.class, short.class, byte.class, + Integer.class, Short.class, Byte.class); + + public static final MethodHandle BSM_BOOLEAN_SWITCH; + public static final MethodHandle BSM_INT_SWITCH; + public static final MethodHandle BSM_LONG_SWITCH; + public static final MethodHandle BSM_FLOAT_SWITCH; + public static final MethodHandle BSM_DOUBLE_SWITCH; + public static final MethodHandle BSM_STRING_SWITCH; + public static final MethodHandle BSM_ENUM_SWITCH; + public static final MethodHandle BSM_TYPE_SWITCH; + + private final static Random random = RandomFactory.getRandom(); + + static { + try { + BSM_BOOLEAN_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "booleanSwitch", + MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, boolean[].class)); + BSM_INT_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "intSwitch", + MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, int[].class)); + BSM_LONG_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "longSwitch", + MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, long[].class)); + BSM_FLOAT_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "floatSwitch", + MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, float[].class)); + BSM_DOUBLE_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "doubleSwitch", + MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, double[].class)); + BSM_STRING_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "stringSwitch", + MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String[].class)); + BSM_ENUM_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "enumSwitch", + MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Class.class, String[].class)); + BSM_TYPE_SWITCH = MethodHandles.lookup().findStatic(SwitchBootstraps.class, "typeSwitch", + MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Class[].class)); + } + catch (NoSuchMethodException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private MethodType switchType(Class<?> target) { + return MethodType.methodType(int.class, target); + } + + private Object box(Class<?> clazz, int i) { + if (clazz == Integer.class) + return i; + else if (clazz == Short.class) + return (short) i; + else if (clazz == Character.class) + return (char) i; + else if (clazz == Byte.class) + return (byte) i; + else + throw new IllegalArgumentException(clazz.toString()); + } + + private void testBoolean(boolean... labels) throws Throwable { + Map<Class<?>, MethodHandle> mhs + = Map.of(boolean.class, ((CallSite) BSM_BOOLEAN_SWITCH.invoke(MethodHandles.lookup(), "", switchType(boolean.class), labels)).dynamicInvoker(), + Boolean.class, ((CallSite) BSM_BOOLEAN_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Boolean.class), labels)).dynamicInvoker()); + + List<Boolean> labelList = new ArrayList<>(); + for (boolean label : labels) + labelList.add(label); + + for (int i=0; i<labels.length; i++) { + assertEquals(i, (int) mhs.get(boolean.class).invokeExact((boolean) labels[i])); + assertEquals(i, (int) mhs.get(Boolean.class).invokeExact((Boolean) labels[i])); + } + + boolean[] booleans = { false, true }; + for (boolean b : booleans) { + if (!labelList.contains(b)) { + assertEquals(labels.length, mhs.get(boolean.class).invoke((boolean) b)); + assertEquals(labels.length, mhs.get(Boolean.class).invoke((boolean) b)); + } + } + + assertEquals(-1, (int) mhs.get(Boolean.class).invoke(null)); + } + + private void testInt(Set<Class<?>> targetTypes, int... labels) throws Throwable { + Map<Class<?>, MethodHandle> mhs + = Map.of(char.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(char.class), labels)).dynamicInvoker(), + byte.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(byte.class), labels)).dynamicInvoker(), + short.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(short.class), labels)).dynamicInvoker(), + int.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(int.class), labels)).dynamicInvoker(), + Character.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Character.class), labels)).dynamicInvoker(), + Byte.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Byte.class), labels)).dynamicInvoker(), + Short.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Short.class), labels)).dynamicInvoker(), + Integer.class, ((CallSite) BSM_INT_SWITCH.invoke(MethodHandles.lookup(), "", switchType(Integer.class), labels)).dynamicInvoker()); + + List<Integer> labelList = IntStream.of(labels) + .boxed() + .collect(Collectors.toList()); + + for (int i=0; i<labels.length; i++) { + // test with invokeExact + if (targetTypes.contains(char.class)) + assertEquals(i, (int) mhs.get(char.class).invokeExact((char) labels[i])); + if (targetTypes.contains(byte.class)) + assertEquals(i, (int) mhs.get(byte.class).invokeExact((byte) labels[i])); + if (targetTypes.contains(short.class)) + assertEquals(i, (int) mhs.get(short.class).invokeExact((short) labels[i])); + if (targetTypes.contains(int.class)) + assertEquals(i, (int) mhs.get(int.class).invokeExact(labels[i])); + if (targetTypes.contains(Integer.class)) + assertEquals(i, (int) mhs.get(Integer.class).invokeExact((Integer) labels[i])); + if (targetTypes.contains(Short.class)) + assertEquals(i, (int) mhs.get(Short.class).invokeExact((Short) (short) labels[i])); + if (targetTypes.contains(Byte.class)) + assertEquals(i, (int) mhs.get(Byte.class).invokeExact((Byte) (byte) labels[i])); + if (targetTypes.contains(Character.class)) + assertEquals(i, (int) mhs.get(Character.class).invokeExact((Character) (char) labels[i])); + + // and with invoke + assertEquals(i, (int) mhs.get(int.class).invoke(labels[i])); + assertEquals(i, (int) mhs.get(Integer.class).invoke(labels[i])); + } + + for (int i=-1000; i<1000; i++) { + if (!labelList.contains(i)) { + assertEquals(labels.length, mhs.get(short.class).invoke((short) i)); + assertEquals(labels.length, mhs.get(Short.class).invoke((short) i)); + assertEquals(labels.length, mhs.get(int.class).invoke(i)); + assertEquals(labels.length, mhs.get(Integer.class).invoke(i)); + if (i >= 0) { +