changeset 56790:892a23eb13ce patterns-deconstruction

Merging patterns and records-and-sealed branches into a new branch patterns-deconstruction.
author jlahoda
date Fri, 14 Jun 2019 11:12:54 +0200
parents 7cb39e5d5420 b5765fdbce5c
children 185f25e1aac8
files src/java.base/share/classes/module-info.java src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Tokens.java src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java src/jdk.jshell/share/classes/jdk/jshell/CompletenessAnalyzer.java test/langtools/jdk/jshell/CompletenessTest.java
diffstat 169 files changed, 9437 insertions(+), 325 deletions(-) [+]
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&trade; 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>
+ * &#064;getter description <br>
+ * &#064;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;
-        }
-        if (based) {
-            flags |= ANONCONSTR_BASED;
-        }
-        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));
-        }
-        result = make.MethodDef(init, make.Block(0, stats));
-        return result;
+    interface DefaultConstructorHelper {
+       Type constructorType();
+       MethodSymbol constructorSymbol();
+       Type enclosingType();
+       TypeSymbol owner();
+       List<Name> superArgs();
+       List<Name> inits();
     }
 
-    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 BasicConstructorHelper implements DefaultConstructorHelper {
+
+        TypeSymbol owner;
+        Type constructorType;
+        MethodSymbol constructorSymbol;
+
+        BasicConstructorHelper(TypeSymbol owner) {
+            this.owner = owner;
         }
-        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 Type constructorType() {
+            if (constructorType == null) {
+                constructorType = new MethodType(List.nil(), syms.voidType, List.nil(), syms.methodClass);
             }
+            return constructorType;
         }
-        return initParams;
+
+        @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;
+        }
+
+        @Override
+        public Type enclosingType() {
+            return Type.noType;
+        }
+
+        @Override
+        public TypeSymbol owner() {
+            return owner;
+        }
+
+        @Override
+        public List<Name> superArgs() {
+            return List.nil();
+        }
+
+        @Override
+        public List<Name> inits() {
+            return List.nil();
+        }
     }
 
-    /** 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 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;
         }
-        List<JCExpression> typeargs = typarams.nonEmpty() ? make.Types(typarams) : null;
-        return make.Exec(make.Apply(typeargs, meth, make.Idents(params)));
+
+        @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;
+        }
+
+        @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;
+        }
+
+        @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);
+        }
+    }
+
+    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;
+        }
+
+        @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) {
+                    assertEquals(labels.length, mhs.get(char.class).invoke((char)i));
+                    assertEquals(labels.length, mhs.get(Character.class).invoke((char)i));
+                }
+                if (i >= -128 && i <= 127) {
+                    assertEquals(labels.length, mhs.get(byte.class).invoke((byte)i));
+                    assertEquals(labels.length, mhs.get(Byte.class).invoke((byte)i));
+                }