diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8b5171e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,637 @@ +[*] +charset = utf-8 +end_of_line = lf + +tab_width = 4 +indent_size = 4 +indent_style = space +max_line_length = 150 +insert_final_newline = false +trim_trailing_whitespace=true + +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = true +ij_smart_tabs = false +ij_visual_guides = none +ij_wrap_on_typing = true + +[*.feature] +indent_size = 2 +ij_gherkin_keep_indents_on_empty_lines = false + +[*.properties] +ij_properties_align_group_field_declarations = false +ij_properties_keep_blank_lines = false +ij_properties_key_value_delimiter = equals +ij_properties_spaces_around_key_value_delimiter = false + +[{*.markdown,*.md}] +ij_markdown_force_one_space_after_blockquote_symbol = true +ij_markdown_force_one_space_after_header_symbol = true +ij_markdown_force_one_space_after_list_bullet = true +ij_markdown_force_one_space_between_words = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_max_lines_around_block_elements = 1 +ij_markdown_max_lines_around_header = 1 +ij_markdown_max_lines_between_paragraphs = 1 +ij_markdown_min_lines_around_block_elements = 1 +ij_markdown_min_lines_around_header = 1 +ij_markdown_min_lines_between_paragraphs = 1 + +[{*.yaml,*.yml}] +indent_size = 2 +ij_yaml_align_values_properties = do_not_align +ij_yaml_autoinsert_sequence_marker = true +ij_yaml_block_mapping_on_new_line = false +ij_yaml_indent_sequence_value = true +ij_yaml_keep_indents_on_empty_lines = false +ij_yaml_keep_line_breaks = true +ij_yaml_sequence_on_new_line = false +ij_yaml_space_before_colon = false +ij_yaml_spaces_within_braces = true +ij_yaml_spaces_within_brackets = true + + +[*.java] +# imports +ij_java_names_count_to_use_import_on_demand = 99 +ij_java_class_count_to_use_import_on_demand = 99 +ij_java_layout_static_imports_separately = true +ij_java_imports_layout = *,|,javax.**,java.**,|,$* +ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.* + +ij_java_align_consecutive_assignments = false +ij_java_align_consecutive_variable_declarations = false +ij_java_align_group_field_declarations = false +ij_java_align_multiline_annotation_parameters = false +ij_java_align_multiline_array_initializer_expression = false +ij_java_align_multiline_assignment = false +ij_java_align_multiline_binary_operation = false +ij_java_align_multiline_chained_methods = false +ij_java_align_multiline_extends_list = false +ij_java_align_multiline_for = true +ij_java_align_multiline_method_parentheses = false +ij_java_align_multiline_parameters = true +ij_java_align_multiline_parameters_in_calls = false +ij_java_align_multiline_parenthesized_expression = false +ij_java_align_multiline_records = true +ij_java_align_multiline_resources = true +ij_java_align_multiline_ternary_operation = false +ij_java_align_multiline_text_blocks = false +ij_java_align_multiline_throws_list = false +ij_java_align_subsequent_simple_methods = false +ij_java_align_throws_keyword = false +ij_java_annotation_parameter_wrap = off +ij_java_array_initializer_new_line_after_left_brace = false +ij_java_array_initializer_right_brace_on_new_line = false +ij_java_array_initializer_wrap = off +ij_java_assert_statement_colon_on_next_line = false +ij_java_assert_statement_wrap = off +ij_java_assignment_wrap = off +ij_java_binary_operation_sign_on_next_line = false +ij_java_binary_operation_wrap = off +ij_java_blank_lines_after_anonymous_class_header = 0 +ij_java_blank_lines_after_class_header = 0 +ij_java_blank_lines_after_imports = 1 +ij_java_blank_lines_after_package = 1 +ij_java_blank_lines_around_class = 1 +ij_java_blank_lines_around_field = 0 +ij_java_blank_lines_around_field_in_interface = 0 +ij_java_blank_lines_around_initializer = 1 +ij_java_blank_lines_around_method = 1 +ij_java_blank_lines_around_method_in_interface = 1 +ij_java_blank_lines_before_class_end = 0 +ij_java_blank_lines_before_imports = 1 +ij_java_blank_lines_before_method_body = 0 +ij_java_blank_lines_before_package = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_at_first_column = true +ij_java_call_parameters_new_line_after_left_paren = false +ij_java_call_parameters_right_paren_on_new_line = false +ij_java_call_parameters_wrap = off +ij_java_case_statement_on_separate_line = true +ij_java_catch_on_new_line = false + +ij_java_class_annotation_wrap = split_into_lines +ij_java_class_brace_style = end_of_line +ij_java_class_names_in_javadoc = 1 + +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_while_brace_force = never +ij_java_doc_add_blank_line_after_description = true +ij_java_doc_add_blank_line_after_param_comments = false +ij_java_doc_add_blank_line_after_return = false +ij_java_doc_add_p_tag_on_empty_lines = true +ij_java_doc_align_exception_comments = true +ij_java_doc_align_param_comments = true +ij_java_doc_do_not_wrap_if_one_line = false +ij_java_doc_enable_formatting = true +ij_java_doc_enable_leading_asterisks = true +ij_java_doc_indent_on_continuation = false +ij_java_doc_keep_empty_lines = true +ij_java_doc_keep_empty_parameter_tag = true +ij_java_doc_keep_empty_return_tag = true +ij_java_doc_keep_empty_throws_tag = true +ij_java_doc_keep_invalid_tags = true +ij_java_doc_param_description_on_new_line = false +ij_java_doc_preserve_line_breaks = false +ij_java_doc_use_throws_not_exception_tag = true +ij_java_else_on_new_line = false +ij_java_enum_constants_wrap = off +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = off +ij_java_field_annotation_wrap = split_into_lines +ij_java_finally_on_new_line = false +ij_java_for_brace_force = never +ij_java_for_statement_new_line_after_left_paren = false +ij_java_for_statement_right_paren_on_new_line = false +ij_java_for_statement_wrap = off +ij_java_generate_final_locals = false +ij_java_generate_final_parameters = true +ij_java_if_brace_force = never +ij_java_indent_case_from_switch = true +ij_java_insert_inner_class_imports = false +ij_java_insert_override_annotation = true +ij_java_keep_blank_lines_before_right_brace = 2 +ij_java_keep_blank_lines_between_package_declaration_and_header = 2 +ij_java_keep_blank_lines_in_code = 2 +ij_java_keep_blank_lines_in_declarations = 2 +ij_java_keep_control_statement_in_one_line = true +ij_java_keep_first_column_comment = true +ij_java_keep_indents_on_empty_lines = false +ij_java_keep_line_breaks = true +ij_java_keep_multiple_expressions_in_one_line = false +ij_java_keep_simple_blocks_in_one_line = false +ij_java_keep_simple_classes_in_one_line = false +ij_java_keep_simple_lambdas_in_one_line = false +ij_java_keep_simple_methods_in_one_line = false +ij_java_label_indent_absolute = false +ij_java_label_indent_size = 0 +ij_java_lambda_brace_style = end_of_line +ij_java_line_comment_add_space = false +ij_java_line_comment_at_first_column = true +ij_java_method_annotation_wrap = split_into_lines +ij_java_method_brace_style = end_of_line +ij_java_method_call_chain_wrap = off +ij_java_method_parameters_new_line_after_left_paren = false +ij_java_method_parameters_right_paren_on_new_line = false +ij_java_method_parameters_wrap = off +ij_java_modifier_list_wrap = false +ij_java_new_line_after_lparen_in_record_header = false +ij_java_parameter_annotation_wrap = off +ij_java_parentheses_expression_new_line_after_left_paren = false +ij_java_parentheses_expression_right_paren_on_new_line = false +ij_java_place_assignment_sign_on_next_line = false +ij_java_prefer_longer_names = true +ij_java_prefer_parameters_wrap = false +ij_java_record_components_wrap = normal +ij_java_repeat_synchronized = true +ij_java_replace_instanceof_and_cast = false +ij_java_replace_null_check = true +ij_java_replace_sum_lambda_with_method_ref = true +ij_java_resource_list_new_line_after_left_paren = false +ij_java_resource_list_right_paren_on_new_line = false +ij_java_resource_list_wrap = off +ij_java_rparen_on_new_line_in_record_header = false + +ij_java_space_after_closing_angle_bracket_in_type_argument = false +ij_java_space_after_colon = true +ij_java_space_after_comma = true +ij_java_space_after_comma_in_type_arguments = true +ij_java_space_after_for_semicolon = true +ij_java_space_after_quest = true +ij_java_space_after_type_cast = true + +ij_java_space_before_annotation_array_initializer_left_brace = false +ij_java_space_before_annotation_parameter_list = false +ij_java_space_before_array_initializer_left_brace = false +ij_java_space_before_catch_keyword = true +ij_java_space_before_catch_left_brace = true +ij_java_space_before_catch_parentheses = true +ij_java_space_before_class_left_brace = true +ij_java_space_before_colon = true +ij_java_space_before_colon_in_foreach = true +ij_java_space_before_comma = false +ij_java_space_before_do_left_brace = true +ij_java_space_before_else_keyword = true +ij_java_space_before_else_left_brace = true +ij_java_space_before_finally_keyword = true +ij_java_space_before_finally_left_brace = true +ij_java_space_before_for_left_brace = true +ij_java_space_before_for_parentheses = true +ij_java_space_before_for_semicolon = false +ij_java_space_before_if_left_brace = true +ij_java_space_before_if_parentheses = true +ij_java_space_before_method_call_parentheses = false +ij_java_space_before_method_left_brace = true +ij_java_space_before_method_parentheses = false +ij_java_space_before_opening_angle_bracket_in_type_parameter = false +ij_java_space_before_quest = true +ij_java_space_before_switch_left_brace = true +ij_java_space_before_switch_parentheses = true +ij_java_space_before_synchronized_left_brace = true +ij_java_space_before_synchronized_parentheses = true +ij_java_space_before_try_left_brace = true +ij_java_space_before_try_parentheses = true +ij_java_space_before_type_parameter_list = false +ij_java_space_before_while_keyword = true +ij_java_space_before_while_left_brace = true +ij_java_space_before_while_parentheses = true + +ij_java_space_inside_one_line_enum_braces = false + +ij_java_space_within_empty_array_initializer_braces = false +ij_java_space_within_empty_method_call_parentheses = false +ij_java_space_within_empty_method_parentheses = false + +ij_java_spaces_around_additive_operators = true +ij_java_spaces_around_assignment_operators = true +ij_java_spaces_around_bitwise_operators = true +ij_java_spaces_around_equality_operators = true +ij_java_spaces_around_lambda_arrow = true +ij_java_spaces_around_logical_operators = true +ij_java_spaces_around_method_ref_dbl_colon = false +ij_java_spaces_around_multiplicative_operators = true +ij_java_spaces_around_relational_operators = true +ij_java_spaces_around_shift_operators = true +ij_java_spaces_around_type_bounds_in_type_parameters = true +ij_java_spaces_around_unary_operator = false + +ij_java_spaces_within_angle_brackets = false +ij_java_spaces_within_annotation_parentheses = false +ij_java_spaces_within_array_initializer_braces = false +ij_java_spaces_within_braces = false +ij_java_spaces_within_brackets = false +ij_java_spaces_within_cast_parentheses = false +ij_java_spaces_within_catch_parentheses = false +ij_java_spaces_within_for_parentheses = false +ij_java_spaces_within_if_parentheses = false +ij_java_spaces_within_method_call_parentheses = false +ij_java_spaces_within_method_parentheses = false +ij_java_spaces_within_parentheses = false +ij_java_spaces_within_record_header = false +ij_java_spaces_within_switch_parentheses = false +ij_java_spaces_within_synchronized_parentheses = false +ij_java_spaces_within_try_parentheses = false +ij_java_spaces_within_while_parentheses = false + +ij_java_special_else_if_treatment = true +ij_java_subclass_name_suffix = Impl +ij_java_ternary_operation_signs_on_next_line = false +ij_java_ternary_operation_wrap = off +ij_java_test_name_suffix = Test +ij_java_throws_keyword_wrap = off +ij_java_throws_list_wrap = off +ij_java_use_external_annotations = false +ij_java_use_fq_class_names = false +ij_java_use_relative_indents = false +ij_java_use_single_class_imports = true +ij_java_variable_annotation_wrap = off +ij_java_visibility = public +ij_java_while_brace_force = never +ij_java_while_on_new_line = false +ij_java_wrap_comments = false +ij_java_wrap_first_method_in_call_chain = false +ij_java_wrap_long_lines = false + +[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.jspx,*.pom,*.rng,*.tagx,*.tld,*.wsdl,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] +ij_xml_align_attributes = true +ij_xml_align_text = false +ij_xml_attribute_wrap = normal +ij_xml_block_comment_at_first_column = true +ij_xml_keep_blank_lines = 2 +ij_xml_keep_indents_on_empty_lines = false +ij_xml_keep_line_breaks = true +ij_xml_keep_line_breaks_in_text = true +ij_xml_keep_whitespaces = false +ij_xml_keep_whitespaces_around_cdata = preserve +ij_xml_keep_whitespaces_inside_cdata = false +ij_xml_line_comment_at_first_column = true +ij_xml_space_after_tag_name = false +ij_xml_space_around_equals_in_attribute = false +ij_xml_space_inside_empty_tag = false +ij_xml_text_wrap = normal + +[{*.avsc,*.har,*.json}] +indent_size = 2 +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = true +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.bash,*.sh,*.zsh}] +indent_size = 2 +tab_width = 2 +ij_shell_binary_ops_start_line = false +ij_shell_keep_column_alignment_padding = false +ij_shell_minify_program = false +ij_shell_redirect_followed_by_space = false +ij_shell_switch_cases_indented = false + +[{*.gant,*.gradle,*.groovy,*.gy}] +ij_groovy_align_group_field_declarations = false +ij_groovy_align_multiline_array_initializer_expression = false +ij_groovy_align_multiline_assignment = false +ij_groovy_align_multiline_binary_operation = false +ij_groovy_align_multiline_chained_methods = false +ij_groovy_align_multiline_extends_list = false +ij_groovy_align_multiline_for = true +ij_groovy_align_multiline_list_or_map = true +ij_groovy_align_multiline_method_parentheses = false +ij_groovy_align_multiline_parameters = true +ij_groovy_align_multiline_parameters_in_calls = false +ij_groovy_align_multiline_resources = true +ij_groovy_align_multiline_ternary_operation = false +ij_groovy_align_multiline_throws_list = false +ij_groovy_align_named_args_in_map = true +ij_groovy_align_throws_keyword = false +ij_groovy_array_initializer_new_line_after_left_brace = false +ij_groovy_array_initializer_right_brace_on_new_line = false +ij_groovy_array_initializer_wrap = off +ij_groovy_assert_statement_wrap = off +ij_groovy_assignment_wrap = off +ij_groovy_binary_operation_wrap = off +ij_groovy_blank_lines_after_class_header = 0 +ij_groovy_blank_lines_after_imports = 1 +ij_groovy_blank_lines_after_package = 1 +ij_groovy_blank_lines_around_class = 1 +ij_groovy_blank_lines_around_field = 0 +ij_groovy_blank_lines_around_field_in_interface = 0 +ij_groovy_blank_lines_around_method = 1 +ij_groovy_blank_lines_around_method_in_interface = 1 +ij_groovy_blank_lines_before_imports = 1 +ij_groovy_blank_lines_before_method_body = 0 +ij_groovy_blank_lines_before_package = 0 +ij_groovy_block_brace_style = end_of_line +ij_groovy_block_comment_at_first_column = true +ij_groovy_call_parameters_new_line_after_left_paren = false +ij_groovy_call_parameters_right_paren_on_new_line = false +ij_groovy_call_parameters_wrap = off +ij_groovy_catch_on_new_line = false +ij_groovy_class_annotation_wrap = split_into_lines +ij_groovy_class_brace_style = end_of_line +ij_groovy_class_count_to_use_import_on_demand = 5 +ij_groovy_do_while_brace_force = never +ij_groovy_else_on_new_line = false +ij_groovy_enum_constants_wrap = off +ij_groovy_extends_keyword_wrap = off +ij_groovy_extends_list_wrap = off +ij_groovy_field_annotation_wrap = split_into_lines +ij_groovy_finally_on_new_line = false +ij_groovy_for_brace_force = never +ij_groovy_for_statement_new_line_after_left_paren = false +ij_groovy_for_statement_right_paren_on_new_line = false +ij_groovy_for_statement_wrap = off +ij_groovy_if_brace_force = never +ij_groovy_import_annotation_wrap = 2 +ij_groovy_imports_layout = *,|,javax.**,java.**,|,$* +ij_groovy_indent_case_from_switch = true +ij_groovy_indent_label_blocks = true +ij_groovy_insert_inner_class_imports = false +ij_groovy_keep_blank_lines_before_right_brace = 2 +ij_groovy_keep_blank_lines_in_code = 2 +ij_groovy_keep_blank_lines_in_declarations = 2 +ij_groovy_keep_control_statement_in_one_line = true +ij_groovy_keep_first_column_comment = true +ij_groovy_keep_indents_on_empty_lines = false +ij_groovy_keep_line_breaks = true +ij_groovy_keep_multiple_expressions_in_one_line = false +ij_groovy_keep_simple_blocks_in_one_line = false +ij_groovy_keep_simple_classes_in_one_line = true +ij_groovy_keep_simple_lambdas_in_one_line = true +ij_groovy_keep_simple_methods_in_one_line = true +ij_groovy_label_indent_absolute = false +ij_groovy_label_indent_size = 0 +ij_groovy_lambda_brace_style = end_of_line +ij_groovy_layout_static_imports_separately = true +ij_groovy_line_comment_add_space = false +ij_groovy_line_comment_at_first_column = true +ij_groovy_method_annotation_wrap = split_into_lines +ij_groovy_method_brace_style = end_of_line +ij_groovy_method_call_chain_wrap = off +ij_groovy_method_parameters_new_line_after_left_paren = false +ij_groovy_method_parameters_right_paren_on_new_line = false +ij_groovy_method_parameters_wrap = off +ij_groovy_modifier_list_wrap = false +ij_groovy_names_count_to_use_import_on_demand = 3 +ij_groovy_parameter_annotation_wrap = off +ij_groovy_parentheses_expression_new_line_after_left_paren = false +ij_groovy_parentheses_expression_right_paren_on_new_line = false +ij_groovy_prefer_parameters_wrap = false +ij_groovy_resource_list_new_line_after_left_paren = false +ij_groovy_resource_list_right_paren_on_new_line = false +ij_groovy_resource_list_wrap = off +ij_groovy_space_after_assert_separator = true +ij_groovy_space_after_colon = true +ij_groovy_space_after_comma = true +ij_groovy_space_after_comma_in_type_arguments = true +ij_groovy_space_after_for_semicolon = true +ij_groovy_space_after_quest = true +ij_groovy_space_after_type_cast = true +ij_groovy_space_before_annotation_parameter_list = false +ij_groovy_space_before_array_initializer_left_brace = false +ij_groovy_space_before_assert_separator = false +ij_groovy_space_before_catch_keyword = true +ij_groovy_space_before_catch_left_brace = true +ij_groovy_space_before_catch_parentheses = true +ij_groovy_space_before_class_left_brace = true +ij_groovy_space_before_closure_left_brace = true +ij_groovy_space_before_colon = true +ij_groovy_space_before_comma = false +ij_groovy_space_before_do_left_brace = true +ij_groovy_space_before_else_keyword = true +ij_groovy_space_before_else_left_brace = true +ij_groovy_space_before_finally_keyword = true +ij_groovy_space_before_finally_left_brace = true +ij_groovy_space_before_for_left_brace = true +ij_groovy_space_before_for_parentheses = true +ij_groovy_space_before_for_semicolon = false +ij_groovy_space_before_if_left_brace = true +ij_groovy_space_before_if_parentheses = true +ij_groovy_space_before_method_call_parentheses = false +ij_groovy_space_before_method_left_brace = true +ij_groovy_space_before_method_parentheses = false +ij_groovy_space_before_quest = true +ij_groovy_space_before_switch_left_brace = true +ij_groovy_space_before_switch_parentheses = true +ij_groovy_space_before_synchronized_left_brace = true +ij_groovy_space_before_synchronized_parentheses = true +ij_groovy_space_before_try_left_brace = true +ij_groovy_space_before_try_parentheses = true +ij_groovy_space_before_while_keyword = true +ij_groovy_space_before_while_left_brace = true +ij_groovy_space_before_while_parentheses = true +ij_groovy_space_in_named_argument = true +ij_groovy_space_in_named_argument_before_colon = false +ij_groovy_space_within_empty_array_initializer_braces = false +ij_groovy_space_within_empty_method_call_parentheses = false +ij_groovy_spaces_around_additive_operators = true +ij_groovy_spaces_around_assignment_operators = true +ij_groovy_spaces_around_bitwise_operators = true +ij_groovy_spaces_around_equality_operators = true +ij_groovy_spaces_around_lambda_arrow = true +ij_groovy_spaces_around_logical_operators = true +ij_groovy_spaces_around_multiplicative_operators = true +ij_groovy_spaces_around_regex_operators = true +ij_groovy_spaces_around_relational_operators = true +ij_groovy_spaces_around_shift_operators = true +ij_groovy_spaces_within_annotation_parentheses = false +ij_groovy_spaces_within_array_initializer_braces = false +ij_groovy_spaces_within_braces = true +ij_groovy_spaces_within_brackets = false +ij_groovy_spaces_within_cast_parentheses = false +ij_groovy_spaces_within_catch_parentheses = false +ij_groovy_spaces_within_for_parentheses = false +ij_groovy_spaces_within_gstring_injection_braces = false +ij_groovy_spaces_within_if_parentheses = false +ij_groovy_spaces_within_list_or_map = false +ij_groovy_spaces_within_method_call_parentheses = false +ij_groovy_spaces_within_method_parentheses = false +ij_groovy_spaces_within_parentheses = false +ij_groovy_spaces_within_switch_parentheses = false +ij_groovy_spaces_within_synchronized_parentheses = false +ij_groovy_spaces_within_try_parentheses = false +ij_groovy_spaces_within_tuple_expression = false +ij_groovy_spaces_within_while_parentheses = false +ij_groovy_special_else_if_treatment = true +ij_groovy_ternary_operation_wrap = off +ij_groovy_throws_keyword_wrap = off +ij_groovy_throws_list_wrap = off +ij_groovy_use_flying_geese_braces = false +ij_groovy_use_fq_class_names = false +ij_groovy_use_fq_class_names_in_javadoc = true +ij_groovy_use_relative_indents = false +ij_groovy_use_single_class_imports = true +ij_groovy_variable_annotation_wrap = off +ij_groovy_while_brace_force = never +ij_groovy_while_on_new_line = false +ij_groovy_wrap_long_lines = false + +[{*.htm,*.html,*.sht,*.shtm,*.shtml}] +ij_html_add_new_line_before_tags = body,div,p,form,h1,h2,h3 +ij_html_align_attributes = true +ij_html_align_text = false +ij_html_attribute_wrap = normal +ij_html_block_comment_at_first_column = true +ij_html_do_not_align_children_of_min_lines = 0 +ij_html_do_not_break_if_inline_tags = title,h1,h2,h3,h4,h5,h6,p +ij_html_do_not_indent_children_of_tags = html,body,thead,tbody,tfoot +ij_html_enforce_quotes = false +ij_html_inline_tags = a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,small,span,strike,strong,sub,sup,textarea,tt,u,var +ij_html_keep_blank_lines = 2 +ij_html_keep_indents_on_empty_lines = false +ij_html_keep_line_breaks = true +ij_html_keep_line_breaks_in_text = true +ij_html_keep_whitespaces = false +ij_html_keep_whitespaces_inside = span,pre,textarea +ij_html_line_comment_at_first_column = true +ij_html_new_line_after_last_attribute = never +ij_html_new_line_before_first_attribute = never +ij_html_quote_style = double +ij_html_remove_new_line_before_tags = br +ij_html_space_after_tag_name = false +ij_html_space_around_equality_in_attribute = false +ij_html_space_inside_empty_tag = false +ij_html_text_wrap = normal +ij_html_uniform_ident = false + +[{*.kt,*.kts,*.main.kts}] +ij_kotlin_align_in_columns_case_branch = false +ij_kotlin_align_multiline_binary_operation = false +ij_kotlin_align_multiline_extends_list = false +ij_kotlin_align_multiline_method_parentheses = false +ij_kotlin_align_multiline_parameters = true +ij_kotlin_align_multiline_parameters_in_calls = false +ij_kotlin_allow_trailing_comma = false +ij_kotlin_allow_trailing_comma_on_call_site = false +ij_kotlin_assignment_wrap = normal +ij_kotlin_blank_lines_after_class_header = 0 +ij_kotlin_blank_lines_around_block_when_branches = 0 +ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 +ij_kotlin_block_comment_at_first_column = true +ij_kotlin_call_parameters_new_line_after_left_paren = true +ij_kotlin_call_parameters_right_paren_on_new_line = true +ij_kotlin_call_parameters_wrap = on_every_item +ij_kotlin_catch_on_new_line = false +ij_kotlin_class_annotation_wrap = split_into_lines +ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL +ij_kotlin_continuation_indent_for_chained_calls = false +ij_kotlin_continuation_indent_for_expression_bodies = false +ij_kotlin_continuation_indent_in_argument_lists = false +ij_kotlin_continuation_indent_in_elvis = false +ij_kotlin_continuation_indent_in_if_conditions = false +ij_kotlin_continuation_indent_in_parameter_lists = false +ij_kotlin_continuation_indent_in_supertype_lists = false +ij_kotlin_else_on_new_line = false +ij_kotlin_enum_constants_wrap = off +ij_kotlin_extends_list_wrap = normal +ij_kotlin_field_annotation_wrap = split_into_lines +ij_kotlin_finally_on_new_line = false +ij_kotlin_if_rparen_on_new_line = true +ij_kotlin_import_nested_classes = false +ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ +ij_kotlin_insert_whitespaces_in_simple_one_line_method = true +ij_kotlin_keep_blank_lines_before_right_brace = 2 +ij_kotlin_keep_blank_lines_in_code = 2 +ij_kotlin_keep_blank_lines_in_declarations = 2 +ij_kotlin_keep_first_column_comment = true +ij_kotlin_keep_indents_on_empty_lines = false +ij_kotlin_keep_line_breaks = true +ij_kotlin_lbrace_on_next_line = false +ij_kotlin_line_comment_add_space = false +ij_kotlin_line_comment_at_first_column = true +ij_kotlin_method_annotation_wrap = split_into_lines +ij_kotlin_method_call_chain_wrap = normal +ij_kotlin_method_parameters_new_line_after_left_paren = true +ij_kotlin_method_parameters_right_paren_on_new_line = true +ij_kotlin_method_parameters_wrap = on_every_item +ij_kotlin_name_count_to_use_star_import = 5 +ij_kotlin_name_count_to_use_star_import_for_members = 3 +ij_kotlin_packages_to_use_import_on_demand = java.util.*,kotlinx.android.synthetic.**,io.ktor.** +ij_kotlin_parameter_annotation_wrap = off +ij_kotlin_space_after_comma = true +ij_kotlin_space_after_extend_colon = true +ij_kotlin_space_after_type_colon = true +ij_kotlin_space_before_catch_parentheses = true +ij_kotlin_space_before_comma = false +ij_kotlin_space_before_extend_colon = true +ij_kotlin_space_before_for_parentheses = true +ij_kotlin_space_before_if_parentheses = true +ij_kotlin_space_before_lambda_arrow = true +ij_kotlin_space_before_type_colon = false +ij_kotlin_space_before_when_parentheses = true +ij_kotlin_space_before_while_parentheses = true +ij_kotlin_spaces_around_additive_operators = true +ij_kotlin_spaces_around_assignment_operators = true +ij_kotlin_spaces_around_equality_operators = true +ij_kotlin_spaces_around_function_type_arrow = true +ij_kotlin_spaces_around_logical_operators = true +ij_kotlin_spaces_around_multiplicative_operators = true +ij_kotlin_spaces_around_range = false +ij_kotlin_spaces_around_relational_operators = true +ij_kotlin_spaces_around_unary_operator = false +ij_kotlin_spaces_around_when_arrow = true +ij_kotlin_variable_annotation_wrap = off +ij_kotlin_while_on_new_line = false +ij_kotlin_wrap_elvis_expressions = 1 +ij_kotlin_wrap_expression_body_functions = 1 +ij_kotlin_wrap_first_method_in_call_chain = false + +[.editorconfig] +ij_editorconfig_align_group_field_declarations = false +ij_editorconfig_space_after_colon = false +ij_editorconfig_space_after_comma = true +ij_editorconfig_space_before_colon = false +ij_editorconfig_space_before_comma = false +ij_editorconfig_spaces_around_assignment_operators = true + + diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a6605d9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: +- package-ecosystem: gradle + directory: "/" + schedule: + interval: daily + time: "19:00" + open-pull-requests-limit: 10 \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..0e8b12d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,58 @@ +name: Java Gradle CI test + +on: [push] + +jobs: + # Run Gradle Wrapper Validation Action to verify the wrapper's checksum +# gradleValidation: ## Skipped till samples is updated/fixed +# name: Gradle Wrapper +# runs-on: ubuntu-latest +# steps: +# +# # Check out current repository +# - name: Fetch Sources +# uses: actions/checkout@v2.3.4 +# +# # Validate wrapper +# - name: Gradle Wrapper Validation +# uses: gradle/wrapper-validation-action@v1.0.3 + test: + runs-on: ubuntu-latest + steps: + - name: Set up Java + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + - uses: actions/checkout@v2 + + # Cache Gradle dependencies + - name: Setup Gradle Dependencies Cache + uses: actions/cache@v2.1.5 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-caches-${{ hashFiles('**/*.gradle', '**/*.gradle.kts', 'gradle.properties') }} + + # Cache Gradle Wrapper + - name: Setup Gradle Wrapper Cache + uses: actions/cache@v2.1.5 + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }} + + - name: Clean + run: ./gradlew clean + + + - name: Test Plugin + run: ./gradlew test + + - name: Build Plugin + run: ./gradlew buildPlugin + + + - name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: Package + path: build/distributions diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ac246c..c46ae16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +### Since 1.0.0 (1 Feb 2021): + + - Fixed bugs + - Refactor build.gradle upgrade dependence versions + - Upgrade JDK 11 + - improve code and clean code refactors + - test: IntelliJ IDEA 2020.3.2 (Community Edition) Build #IC-203.xx + ### Since 0.12.0 (11 Apr 2018): - Fixed issues with metadata that contains multiple references @@ -49,5 +57,5 @@ - Updated correct path for animation in plugin.xml ### Since 0.1.0 (30 Nov 2017): - + - Initial support for Spring Assistant from both libraries & `@ConfigurationProperties` classes diff --git a/README.md b/README.md index 24d08a6..6f5fe83 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,8 @@ Feel free to let me know what else you want added via the [issues](https://githu Suggestions, feedback and other comments welcome via [@1tontech](https://twitter.com/1tontech) on Twitter +[intellij-spring-assistant-1.0.1.zip](dist/intellij-spring-assistant-1.0.1.zip) + ## Changelog See [here](CHANGELOG.md) diff --git a/build.gradle b/build.gradle index 46ee8b9..a581ceb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,61 +1,95 @@ -import com.vladsch.flexmark.ast.Document import com.vladsch.flexmark.html.HtmlRenderer import com.vladsch.flexmark.parser.Parser - -version '0.12.0' +import com.vladsch.flexmark.util.ast.Document buildscript { - apply plugin: 'groovy' - - repositories { - mavenCentral() - maven { - url "https://oss.sonatype.org/content/repositories/snapshots/" - } - maven { - url 'http://dl.bintray.com/jetbrains/intellij-plugin-service' - } - maven { - url "https://plugins.gradle.org/m2/" - } - } dependencies { - classpath "gradle.plugin.org.jetbrains.intellij.plugins:gradle-intellij-plugin:0.2.18" - classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.1' - classpath 'org.codehaus.groovy:groovy-all:2.4.13' - classpath group: 'com.vladsch.flexmark', name: 'flexmark', version: '0.28.12' + classpath 'com.vladsch.flexmark:flexmark:0.62.2' // '0.28.12' } } -apply plugin: 'java' -apply plugin: 'org.jetbrains.intellij' -apply plugin: 'org.junit.platform.gradle.plugin' +plugins { + id 'java' + id 'idea' + id 'org.jetbrains.intellij' version '0.7.3' +} + -sourceCompatibility = 1.8 +group = 'in.oneton.idea.spring' +version = '1.0.1' + +sourceCompatibility = JavaVersion.VERSION_11 +targetCompatibility = JavaVersion.VERSION_11 + +compileJava.options.encoding = 'UTF-8' +compileTestJava.options.encoding = 'UTF-8' +compileJava.options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" repositories { + mavenLocal() mavenCentral() } +configurations { + compileOnly { + extendsFrom annotationProcessor + } + testCompileOnly { + extendsFrom annotationProcessor + } +} + dependencies { - compileOnly 'org.projectlombok:lombok:1.16.20' - compile 'org.apache.commons:commons-collections4:4.1' - compile 'com.miguelfonseca.completely:completely-core:0.8.0' - testCompile 'org.junit.jupiter:junit-jupiter-api:5.0.1' - testRuntime 'org.junit.jupiter:junit-jupiter-engine:5.0.1' - testCompile group: 'org.mockito', name: 'mockito-core', version: '2.12.0' + ['annotationProcessor', 'compileOnly', 'testAnnotationProcessor', 'testCompileOnly'] + .each { conf -> add(conf, 'org.projectlombok:lombok:1.18.20') } + + implementation 'org.apache.commons:commons-collections4:4.4' + implementation 'com.miguelfonseca.completely:completely-core:0.9.0' + implementation 'com.vladsch.flexmark:flexmark:0.62.2' + + testImplementation(platform('org.junit:junit-bom:5.7.2')) + testImplementation 'org.mockito:mockito-core:3.10.0' + testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'org.junit.jupiter:junit-jupiter-api' +} + +clean.doFirst { + delete 'build', 'out' +} + +// See https://github.com/JetBrains/gradle-intellij-plugin/ +intellij { + type = 'IC' + version = '2021.1.1' + pluginName = 'intellij-spring-assistant' + plugins = ['java', 'properties', 'yaml', 'maven', 'gradle'] + updateSinceUntilBuild false +} + +publishPlugin { + token System.getenv('IJ_PLUGIN_TOKEN') +// username System.getProperty('ij_plugin_portal_login') +// password System.getProperty('ij_plugin_portal_password') + channels 'eap', 'nightly', 'default' +} + + +patchPluginXml { + sinceBuild '203.1.0' + untilBuild '211.*' + pluginDescription readmeXmlAsHtml() + changeNotes changeLogAsHtml() } String readmeXmlAsHtml() { - Parser parser = Parser.builder().build() - HtmlRenderer renderer = HtmlRenderer.builder().build() - String readmeContent = new File(rootProject.uri('README.md')).text + final String readmeContent = new File(rootProject.uri('README.md')).text // since these images needs to shown from within intellij, lest put absolute urls so that the images & changelog will be visible - readmeContent = readmeContent.replaceAll("help\\.gif", "https://raw.githubusercontent.com/1tontech/intellij-spring-assistant/" + version + "/help.gif") - readmeContent = readmeContent.replaceAll("CHANGELOG.md", "https://github.com/1tontech/intellij-spring-assistant/blob/" + version + "/CHANGELOG.md") - Document readmeDocument = parser.parse(readmeContent) - renderer.render(readmeDocument) + .replaceAll("help\\.gif", "https://raw.githubusercontent.com/1tontech/intellij-spring-assistant/" + version + "/help.gif") + .replaceAll("CHANGELOG.md", "https://github.com/1tontech/intellij-spring-assistant/blob/" + version + "/CHANGELOG.md") + + final Document readmeDocument = Parser.builder().build().parse(readmeContent) + HtmlRenderer.builder().build().render(readmeDocument) } String changeLogAsHtml() { @@ -65,18 +99,3 @@ String changeLogAsHtml() { renderer.render(changeLogDocument) } -intellij { - version 'IC-2017.2' - plugins = ['properties', 'yaml', 'maven', 'gradle', 'Lombook Plugin:0.16-2017.2'] - downloadSources true - patchPluginXml { - pluginDescription readmeXmlAsHtml() - changeNotes changeLogAsHtml() - } - publishPlugin { - username System.getProperty('ij_plugin_portal_login') - password System.getProperty('ij_plugin_portal_password') - channels 'eap', 'nightly', 'default' - } - updateSinceUntilBuild false -} diff --git a/dist/intellij-spring-assistant-1.0.1.zip b/dist/intellij-spring-assistant-1.0.1.zip new file mode 100644 index 0000000..28c28c6 Binary files /dev/null and b/dist/intellij-spring-assistant-1.0.1.zip differ diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..9074c6b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +name='intellij-spring-assistant' +group='in.oneton.idea.spring' +version='1.0.1' \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 6ffa237..e708b1c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aefc3ec..0f80bbf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Feb 17 23:27:53 IST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip diff --git a/gradlew b/gradlew index 9aa616c..4f906e0 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,20 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ############################################################################## ## @@ -28,16 +44,16 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" -warn ( ) { +warn () { echo "$*" } -die ( ) { +die () { echo echo "$*" echo @@ -66,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -109,10 +126,11 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -138,32 +156,30 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=`save "$@"` -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then - cd "$(dirname "$0")" -fi +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index f955316..ac1b06f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,84 +1,89 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/gradle/single-project/build.gradle b/samples/gradle/single-project/build.gradle index 1194a1b..be23f28 100644 --- a/samples/gradle/single-project/build.gradle +++ b/samples/gradle/single-project/build.gradle @@ -1,45 +1,48 @@ -buildscript { - ext { - springBootVersion = '2.0.0.RC2' - } - repositories { - mavenCentral() - maven { url "https://repo.spring.io/snapshot" } - maven { url "https://repo.spring.io/milestone" } - } - dependencies { - classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") - } +plugins { + id 'java' + id 'idea' + id 'org.springframework.boot' version '2.4.2' + id 'io.spring.dependency-management' version '1.0.11.RELEASE' } -apply plugin: 'java' -apply plugin: 'org.springframework.boot' -apply plugin: 'io.spring.dependency-management' - group = 'com.acme' version = '0.0.1-SNAPSHOT' -sourceCompatibility = 1.8 +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +compileJava.options.encoding = "UTF-8" +compileTestJava.options.encoding = "UTF-8" + +compileJava.dependsOn(processResources) repositories { - mavenCentral() - maven { url "https://repo.spring.io/snapshot" } - maven { url "https://repo.spring.io/milestone" } + mavenCentral() + maven { url "https://repo.spring.io/snapshot" } + maven { url "https://repo.spring.io/milestone" } +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } } dependencies { - compileOnly 'org.projectlombok:lombok:1.16.18' -// compileOnly "org.springframework.boot:spring-boot-configuration-processor" + ['annotationProcessor', 'compileOnly', 'testAnnotationProcessor', 'testCompileOnly'] + .each { conf -> add(conf, 'org.projectlombok:lombok') } + + implementation 'org.springframework.boot:spring-boot-starter-actuator' + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' + // compile('org.springframework.boot:spring-boot') // compile('org.springframework.boot:spring-boot-devtools') // compile('org.springframework.boot:spring-boot-autoconfigure') // compile('org.springframework.boot:spring-boot-starter-data-jpa') // compile('org.springframework.boot:spring-boot-starter-security') // compile('org.springframework.boot:spring-boot-starter-web') - compile('org.springframework.boot:spring-boot-starter-actuator') // testCompile('org.springframework.boot:spring-boot-starter-test') // testCompile('org.springframework.security:spring-security-test') // https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-config // compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-config', version: '2.0.0.M4' } -compileJava.dependsOn(processResources) diff --git a/samples/gradle/single-project/gradle/wrapper/gradle-wrapper.properties b/samples/gradle/single-project/gradle/wrapper/gradle-wrapper.properties index 180babc..121a922 100644 --- a/samples/gradle/single-project/gradle/wrapper/gradle-wrapper.properties +++ b/samples/gradle/single-project/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-all.zip diff --git a/samples/gradle/single-project/settings.gradle b/samples/gradle/single-project/settings.gradle new file mode 100644 index 0000000..af66929 --- /dev/null +++ b/samples/gradle/single-project/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'single-project' diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..078c961 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,13 @@ +pluginManagement { + + repositories { + mavenCentral() + maven { url 'https://oss.sonatype.org/content/repositories/snapshots' } + maven { url 'https://dl.bintray.com/jetbrains/intellij-plugin-service' } + maven { url 'https://plugins.gradle.org/m2/' } + + gradlePluginPortal() + } +} + +rootProject.name = 'intellij-spring-assistant' diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/GradleModuleBuilderPostProcessor.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/GradleModuleBuilderPostProcessor.java index 390d6e1..4d54282 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/GradleModuleBuilderPostProcessor.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/GradleModuleBuilderPostProcessor.java @@ -1,37 +1,39 @@ package in.oneton.idea.spring.assistant.plugin.initializr; import com.intellij.ide.util.newProjectWizard.AddModuleWizard; -import com.intellij.openapi.externalSystem.service.project.ProjectDataManager; import com.intellij.openapi.module.Module; -import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; -import org.jetbrains.plugins.gradle.service.project.wizard.GradleProjectImportBuilder; -import org.jetbrains.plugins.gradle.service.project.wizard.GradleProjectImportProvider; +import com.intellij.projectImport.ProjectImportBuilder; +import com.intellij.projectImport.ProjectImportProvider; -import static com.intellij.openapi.components.ServiceManager.getService; import static in.oneton.idea.spring.assistant.plugin.misc.PsiCustomUtil.findFileUnderRootInModule; public class GradleModuleBuilderPostProcessor implements ModuleBuilderPostProcessor { + + /** + * TODO: Find a way to use GradleModuleBuilder instead of GradleProjectImportBuilder when adding a child module to the parent + */ @Override - public boolean postProcess(Module module) { - // TODO: Find a way to use GradleModuleBuilder instead of GradleProjectImportBuilder when adding a child module to the parent - Project project = module.getProject(); - VirtualFile gradleFile = findFileUnderRootInModule(module, "build.gradle"); - if (gradleFile == null) { // not a gradle project + public boolean postProcess(final Module module) { + final VirtualFile gradleFile = findFileUnderRootInModule(module, "build.gradle"); + + if (gradleFile == null) {// not a gradle project + return true; + } + + final ProjectImportBuilder importBuilder = ProjectImportBuilder.EXTENSIONS_POINT_NAME.getExtensions()[0]; + final ProjectImportProvider importProvider = ProjectImportProvider.PROJECT_IMPORT_PROVIDER.getExtensions()[0]; + + final AddModuleWizard addModuleWizard = new AddModuleWizard(module.getProject(), gradleFile.getPath(), importProvider); + + if (addModuleWizard.getStepCount() > 0 && !addModuleWizard.showAndGet()) { // user has cancelled import project prompt return true; - } else { - ProjectDataManager projectDataManager = getService(ProjectDataManager.class); - GradleProjectImportBuilder importBuilder = new GradleProjectImportBuilder(projectDataManager); - GradleProjectImportProvider importProvider = new GradleProjectImportProvider(importBuilder); - AddModuleWizard addModuleWizard = - new AddModuleWizard(project, gradleFile.getPath(), importProvider); - if (addModuleWizard.getStepCount() > 0 && !addModuleWizard - .showAndGet()) { // user has cancelled import project prompt - return true; - } else { // user chose to import via the gradle import prompt - importBuilder.commit(project, null, null); - return false; - } } + + // user chose to import via the gradle import prompt + importBuilder.commit(module.getProject(), null, null); + + return false; } + } diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/InitializrModuleBuilder.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/InitializrModuleBuilder.java index ff5aca4..364ea16 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/InitializrModuleBuilder.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/InitializrModuleBuilder.java @@ -1,5 +1,6 @@ package in.oneton.idea.spring.assistant.plugin.initializr; +import com.intellij.ide.util.projectWizard.JavaModuleBuilder; import com.intellij.ide.util.projectWizard.ModuleBuilder; import com.intellij.ide.util.projectWizard.ModuleWizardStep; import com.intellij.ide.util.projectWizard.SettingsStep; @@ -16,12 +17,12 @@ import com.intellij.openapi.roots.LanguageLevelModuleExtension; import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.roots.ui.configuration.ModulesProvider; -import com.intellij.openapi.util.InvalidDataException; import com.intellij.pom.java.LanguageLevel; import in.oneton.idea.spring.assistant.plugin.initializr.step.DependencySelectionStep; import in.oneton.idea.spring.assistant.plugin.initializr.step.ProjectDetailsStep; import in.oneton.idea.spring.assistant.plugin.initializr.step.ServerSelectionStep; import in.oneton.idea.spring.assistant.plugin.misc.Icons; +import lombok.EqualsAndHashCode; import org.jdom.JDOMException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,6 +39,7 @@ import static com.intellij.openapi.ui.Messages.showErrorDialog; import static com.intellij.pom.java.LanguageLevel.parse; +@EqualsAndHashCode(callSuper = true) public class InitializrModuleBuilder extends ModuleBuilder { public static final String CONTENT_TYPE = "application/vnd.initializr.v2.1+json"; @@ -45,75 +47,78 @@ public class InitializrModuleBuilder extends ModuleBuilder { private ProjectCreationRequest request; @Override - public void setupRootModel(ModifiableRootModel modifiableRootModel) { - Sdk moduleOrProjectSdk = getModuleJdk() != null ? - getModuleJdk() : - getInstance(modifiableRootModel.getProject()).getProjectSdk(); + public void setupRootModel(final ModifiableRootModel modifiableRootModel) { + final Sdk moduleOrProjectSdk = this.getModuleJdk() != null ? + this.getModuleJdk() : + getInstance(modifiableRootModel.getProject()).getProjectSdk(); if (moduleOrProjectSdk != null) { modifiableRootModel.setSdk(moduleOrProjectSdk); } - LanguageLevelModuleExtension languageLevelModuleExtension = - modifiableRootModel.getModuleExtension(LanguageLevelModuleExtension.class); + final LanguageLevelModuleExtension languageLevelModuleExtension = + modifiableRootModel.getModuleExtension(LanguageLevelModuleExtension.class); if (languageLevelModuleExtension != null && moduleOrProjectSdk != null) { - if (safeGetProjectCreationRequest().isJavaVersionSet()) { - LanguageLevel lastSelectedLanguageLevel = - parse(safeGetProjectCreationRequest().getJavaVersion().getId()); + if (this.safeGetProjectCreationRequest().isJavaVersionSet()) { + final LanguageLevel lastSelectedLanguageLevel = + parse(this.safeGetProjectCreationRequest().getJavaVersion().getId()); if (lastSelectedLanguageLevel != null) { - JavaSdkVersion lastSelectedJavaSdkVersion = fromLanguageLevel(lastSelectedLanguageLevel); - JavaSdkVersion moduleOrProjectLevelJavaSdkVersion = - getInstance().getVersion(moduleOrProjectSdk); + final JavaSdkVersion lastSelectedJavaSdkVersion = fromLanguageLevel(lastSelectedLanguageLevel); + final JavaSdkVersion moduleOrProjectLevelJavaSdkVersion = + getInstance().getVersion(moduleOrProjectSdk); if (moduleOrProjectLevelJavaSdkVersion != null && moduleOrProjectLevelJavaSdkVersion - .isAtLeast(lastSelectedJavaSdkVersion)) { + .isAtLeast(lastSelectedJavaSdkVersion)) { languageLevelModuleExtension.setLanguageLevel(lastSelectedLanguageLevel); } } } } - doAddContentEntry(modifiableRootModel); + this.doAddContentEntry(modifiableRootModel); } + @Override @Nullable - public ModuleWizardStep modifySettingsStep(@NotNull SettingsStep settingsStep) { - JTextField moduleNameField = settingsStep.getModuleNameField(); + public ModuleWizardStep modifySettingsStep(@NotNull final SettingsStep settingsStep) { + final JTextField moduleNameField = settingsStep.getModuleNameField(); if (moduleNameField != null) { - moduleNameField.setText(request.getArtifactId()); + moduleNameField.setText(this.request.getArtifactId()); } return super.modifySettingsStep(settingsStep); } - public ModuleWizardStep[] createWizardSteps(@NotNull WizardContext wizardContext, - @NotNull ModulesProvider modulesProvider) { - return new ModuleWizardStep[] {new ProjectDetailsStep(this, wizardContext), - new DependencySelectionStep(this)}; + @Override + public ModuleWizardStep[] createWizardSteps(@NotNull final WizardContext wizardContext, + @NotNull final ModulesProvider modulesProvider) { + return new ModuleWizardStep[]{new ProjectDetailsStep(this, wizardContext), + new DependencySelectionStep(this)}; } + @Override @Nullable - public ModuleWizardStep getCustomOptionsStep(WizardContext context, Disposable parentDisposable) { + public ModuleWizardStep getCustomOptionsStep(final WizardContext context, final Disposable parentDisposable) { return new ServerSelectionStep(this); } @NotNull @Override - public Module createModule(@NotNull ModifiableModuleModel moduleModel) - throws InvalidDataException, IOException, ModuleWithNameAlreadyExists, JDOMException, - ConfigurationException { - Module module = super.createModule(moduleModel); + public Module createModule(@NotNull final ModifiableModuleModel moduleModel) + throws IOException, ModuleWithNameAlreadyExists, JDOMException, ConfigurationException { + + final Module module = super.createModule(moduleModel); + getApplication().invokeLater(() -> { ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { try { - InitializerDownloader downloader = new InitializerDownloader(this); + final InitializerDownloader downloader = new InitializerDownloader(this); downloader.execute(ProgressManager.getInstance().getProgressIndicator()); - } catch (IOException var2) { - getApplication() - .invokeLater(() -> showErrorDialog("Error: " + var2.getMessage(), "Creation Failed")); + } catch (final IOException var2) { + getApplication().invokeLater(() -> showErrorDialog("Error: " + var2.getMessage(), "Creation Failed")); } - }, "Downloading Required Files...", true, null); - ModuleBuilderPostProcessor[] postProcessors = - ModuleBuilderPostProcessor.EXTENSION_POINT_NAME.getExtensions(); - for (ModuleBuilderPostProcessor postProcessor : postProcessors) { + }, "Downloading required files...", true, null); + final ModuleBuilderPostProcessor[] postProcessors = + ModuleBuilderPostProcessor.EXTENSION_POINT_NAME.getExtensions(); + for (final ModuleBuilderPostProcessor postProcessor : postProcessors) { if (!postProcessor.postProcess(module)) { return; } @@ -135,7 +140,7 @@ public String getBuilderId() { @Override public String getDescription() { - return "Bootstrap spring applications using Spring Boot & Spring Cloud Dataflow starters"; + return "Bootstrap spring applications using spring boot & spring cloud dataflow starters"; } @Override @@ -143,19 +148,20 @@ public String getPresentableName() { return "Spring Assistant"; } + @Override public String getParentGroup() { return "Build Tools"; } @Override - public ModuleType getModuleType() { + public ModuleType getModuleType() { return JAVA; } public ProjectCreationRequest safeGetProjectCreationRequest() { - if (request == null) { - request = new ProjectCreationRequest(); + if (this.request == null) { + this.request = new ProjectCreationRequest(); } - return request; + return this.request; } } diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/metadata/InitializerMetadata.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/metadata/InitializerMetadata.java index a12b8c7..c3a03bc 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/metadata/InitializerMetadata.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/metadata/InitializerMetadata.java @@ -8,10 +8,9 @@ import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; import lombok.ToString; +import lombok.experimental.FieldNameConstants; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; @@ -22,200 +21,194 @@ @ToString public class InitializerMetadata { - @SerializedName("dependencies") - private DependencyComposite dependencyComposite; + @SerializedName("dependencies") + private DependencyComposite dependencyComposite; - @SerializedName("type") - private ProjectTypeComposite projectTypeComposite; + @SerializedName("type") + private ProjectTypeComposite projectTypeComposite; - @SerializedName("packaging") - private IdAndNameComposite packagingTypeComposite; + @SerializedName("packaging") + private IdAndNameComposite packagingTypeComposite; - @SerializedName("javaVersion") - private IdAndNameComposite javaVersionComposite; + @SerializedName("javaVersion") + private IdAndNameComposite javaVersionComposite; - @SerializedName("language") - private IdAndNameComposite languageComposite; + @SerializedName("language") + private IdAndNameComposite languageComposite; - @SerializedName("bootVersion") - private IdAndNameComposite bootVersionComposite; + @SerializedName("bootVersion") + private IdAndNameComposite bootVersionComposite; - @SerializedName("groupId") - private DefaultValueHolder groupIdHolder; + @SerializedName("groupId") + private DefaultValueHolder groupIdHolder; - @SerializedName("artifactId") - private DefaultValueHolder artifactIdHolder; + @SerializedName("artifactId") + private DefaultValueHolder artifactIdHolder; - @SerializedName("version") - private DefaultValueHolder versionHolder; + @SerializedName("version") + private DefaultValueHolder versionHolder; - @SerializedName("name") - private DefaultValueHolder nameHolder; + @SerializedName("name") + private DefaultValueHolder nameHolder; - @SerializedName("description") - private DefaultValueHolder descriptionHolder; + @SerializedName("description") + private DefaultValueHolder descriptionHolder; - @SerializedName("packageName") - private DefaultValueHolder packageNameHolder; + @SerializedName("packageName") + private DefaultValueHolder packageNameHolder; - public interface IdContainer { - String getId(); - } + public interface IdContainer { + String getId(); + } - // TODO: Not sure why uncommenting the line below is making the compilation fail - // @Data - // @ToString - public static class DependencyComposite { - @Getter - @Setter - @SerializedName("values") - private List groups; + @Data + public static class DependencyComposite { - @NotNull - public Optional findGroupForDependency(Dependency dependency) { - return groups.stream() - .filter(group -> group.getDependencies().stream().anyMatch(dep -> dep.equals(dependency))) - .findFirst(); - } + @SerializedName("values") + private List groups; - // TODO: Not sure why uncommenting the line below is making the compilation fail - // @Data - // @EqualsAndHashCode(of = "name") - public static class DependencyGroup { - @Getter - @Setter - private String name; - @Getter - @Setter - @SerializedName("values") - private List dependencies; - - @Override - public int hashCode() { - return name.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return obj != null && obj instanceof DependencyGroup && ((DependencyGroup) obj).name != null - && name.equals(((DependencyGroup) obj).name); - } - - @Override - public String toString() { - return name; - } - - - @Data - @EqualsAndHashCode(of = "id") - public static class Dependency { - private String id; - private String name; - private String description; - @Nullable - private VersionRange versionRange; - @Nullable - @SerializedName("_links") - private DependencyLinksContainer linksContainer; - - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public boolean isVersionCompatible(Version bootVersion) { - return versionRange == null || versionRange.match(bootVersion); + @NotNull + public Optional findGroupForDependency(Dependency dependency) { + return groups.stream() + .filter(group -> group.getDependencies().stream().anyMatch(dep -> dep.equals(dependency))) + .findFirst(); } - @Data - public static class DependencyLinksContainer { - @Nullable - @SerializedName("reference") - private List references; - @Nullable - @SerializedName("guide") - private List guides; - - - @Getter - @Setter - @Builder - @NoArgsConstructor - @AllArgsConstructor - @ToString - public static class DependencyLink { - private String href; - @Nullable - private String title; - private boolean templated; - - @NotNull - public String getHrefAfterReplacement(String bootVersion) { - if (templated) { - return href.replaceAll("\\{bootVersion}", bootVersion); - } - return href; + @FieldNameConstants + @ToString(onlyExplicitlyIncluded = true) + @EqualsAndHashCode(onlyExplicitlyIncluded = true) + public static class DependencyGroup { + + @ToString.Include + @EqualsAndHashCode.Include + private String name; + + @SerializedName("values") + private List dependencies; + + + @Data + @EqualsAndHashCode(onlyExplicitlyIncluded = true) + public static class Dependency { + + @EqualsAndHashCode.Include + private String id; + private String name; + private String description; + + @Nullable + private VersionRange versionRange; + + @Nullable + @SerializedName("_links") + private DependencyLinksContainer linksContainer; + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public boolean isVersionCompatible(Version bootVersion) { + return versionRange == null || versionRange.match(bootVersion); + } + + + @Data + public static class DependencyLinksContainer { + + @Nullable + @SerializedName("reference") + private List references; + + @Nullable + @SerializedName("guide") + private List guides; + + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class DependencyLink { + + private String href; + + @Nullable + private String title; + private boolean templated; + + @NotNull + public String getHrefAfterReplacement(final String bootVersion) { + if (templated) { //TODO: > Analisar replace + return href.replace("\\{bootVersion}", bootVersion); + } + return href; + } + } + } } - } } - } } - } - @Data - public static class ProjectTypeComposite { - @SerializedName("default") - private String defaultValue; - @SerializedName("values") - private List types; + @Data + public static class ProjectTypeComposite { + + @SerializedName("default") + private String defaultValue; + + @SerializedName("values") + private List types; + + + @Data + @ToString(onlyExplicitlyIncluded = true) + @EqualsAndHashCode(onlyExplicitlyIncluded = true) + public static class ProjectType implements IdContainer { + + @EqualsAndHashCode.Include + private String id; + + @ToString.Include + private String name; + + private String description; + private String action; + } + } @Data - @EqualsAndHashCode(of = "id") - public static class ProjectType implements IdContainer { - private String id; - private String name; - private String description; - private String action; - - @Override - public String toString() { - return name; - } + public static class IdAndNameComposite { + + @SerializedName("default") + private String defaultValue; + + private List values; } - } - @Data - public static class IdAndNameComposite { - @SerializedName("default") - private String defaultValue; - private List values; - } + @Data + @ToString(onlyExplicitlyIncluded = true) + @EqualsAndHashCode(onlyExplicitlyIncluded = true) + public static class IdAndName implements IdContainer { + @EqualsAndHashCode.Include + private String id; - @Data - @EqualsAndHashCode(of = "id") - public static class IdAndName implements IdContainer { - private String id; - private String name; + @ToString.Include + private String name; - @Override - public String toString() { - return name; + public Version parseIdAsVersion() { + return Version.parse(id); + } } - public Version parseIdAsVersion() { - return Version.parse(id); - } - } + @Data + public static class DefaultValueHolder { - @Data - public static class DefaultValueHolder { - @SerializedName("default") - private String defaultValue; - } + @SerializedName("default") + private String defaultValue; + } } diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/step/ProjectDetails.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/step/ProjectDetails.java index 60d7678..031a69c 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/step/ProjectDetails.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/step/ProjectDetails.java @@ -16,6 +16,7 @@ import in.oneton.idea.spring.assistant.plugin.initializr.metadata.InitializerMetadata.IdAndNameComposite; import in.oneton.idea.spring.assistant.plugin.initializr.metadata.InitializerMetadata.ProjectTypeComposite; import in.oneton.idea.spring.assistant.plugin.initializr.metadata.InitializerMetadata.ProjectTypeComposite.ProjectType; +import org.jetbrains.jps.model.java.JpsJavaSdkType; import javax.swing.*; import java.util.List; @@ -178,12 +179,10 @@ public boolean validate(ModuleBuilder moduleBuilder, WizardContext wizardContext throw new ConfigurationException("Invalid package", "Invalid Data"); } else if (!request.hasCompatibleJavaVersion(moduleBuilder, wizardContext)) { JavaSdkVersion wizardSdkVersion = from(wizardContext, moduleBuilder); - throw new ConfigurationException("Selected Java version " + requireNonNull( - IdAndName.class.cast(javaVersion.getSelectedItem())).getName() - + " is not supported. Max supported version is (" + requireNonNull(wizardSdkVersion) - .getMaxLanguageLevel().getCompilerComplianceDefaultOption() - + ").\n\n You can go back to first screen and change the Project/Module SDK version there if you need support for newer Java versions", - "Java Compatibility"); + throw new ConfigurationException("Selected Java version " + requireNonNull((IdAndName) this.javaVersion.getSelectedItem()).getName() + + " is not supported. Max supported version is (" + JpsJavaSdkType.complianceOption(wizardSdkVersion.getMaxLanguageLevel().toJavaVersion()) + + ").\n\n You can go back to first screen and change the Project/Module SDK version there if you need support for newer Java versions", + "Java Compatibility"); } return true; } diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/step/SelectedDependenciesTableModel.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/step/SelectedDependenciesTableModel.java index b5c6c25..e7d64a3 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/step/SelectedDependenciesTableModel.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/step/SelectedDependenciesTableModel.java @@ -26,8 +26,7 @@ import java.util.ArrayList; import java.util.List; -import static com.intellij.icons.AllIcons.Modules.DeleteContentFolder; -import static com.intellij.icons.AllIcons.Modules.DeleteContentFolderRollover; +import static com.intellij.icons.AllIcons.Modules.ExcludeRoot; import static com.intellij.ui.speedSearch.SpeedSearchUtil.applySpeedSearchHighlighting; import static in.oneton.idea.spring.assistant.plugin.initializr.misc.InitializrUtil.resetTableLookAndFeelToSingleSelect; import static java.awt.event.MouseEvent.BUTTON1; @@ -189,7 +188,7 @@ class InplaceButtonTableCellRenderer extends DefaultTableCellRenderer { public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { InplaceButton inplaceButton = new InplaceButton( - new IconButton("Click to delete", DeleteContentFolder, DeleteContentFolderRollover), + new IconButton("Click to delete", ExcludeRoot, ExcludeRoot), e -> { }); Couple colors = UIUtil.getCellColors(table, isSelected, row, column); diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/step/SelectedDependencyListItem.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/step/SelectedDependencyListItem.java index 805a44d..df3256b 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/step/SelectedDependencyListItem.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/initializr/step/SelectedDependencyListItem.java @@ -6,10 +6,10 @@ import javax.swing.*; -import static com.intellij.icons.AllIcons.Modules.DeleteContentFolder; -import static com.intellij.icons.AllIcons.Modules.DeleteContentFolderRollover; +import static com.intellij.icons.AllIcons.Modules.ExcludeRoot; public class SelectedDependencyListItem { + private SelectedDependencyListItemListener listener; private InplaceButton deleteButton; @@ -18,17 +18,15 @@ public class SelectedDependencyListItem { public void init(String name, SelectedDependencyListItemListener listener) { this.listener = listener; - nameLabel.setText(name); + this.nameLabel.setText(name); } private void createUIComponents() { - deleteButton = new InplaceButton( - new IconButton("Click to delete", DeleteContentFolder, DeleteContentFolderRollover), - e -> listener.onDeleteClicked()); + this.deleteButton = new InplaceButton(new IconButton("Click to delete", ExcludeRoot, ExcludeRoot), e -> this.listener.onDeleteClicked()); } public JPanel getRoot() { - return root; + return this.root; } interface SelectedDependencyListItemListener { diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/misc/GenericUtil.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/misc/GenericUtil.java index 112825b..ff60e9e 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/misc/GenericUtil.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/misc/GenericUtil.java @@ -9,6 +9,7 @@ import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import in.oneton.idea.spring.assistant.plugin.suggestion.SuggestionNode; import lombok.experimental.UtilityClass; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.yaml.psi.YAMLKeyValue; @@ -36,161 +37,159 @@ @UtilityClass public class GenericUtil { - private static final Pattern PACKAGE_REMOVAL_PATTERN = - Pattern.compile("[a-zA-Z_][a-zA-Z_0-9]*\\."); - private static final Pattern GENERIC_SECTION_REMOVAL_PATTERN = - Pattern.compile("<(?[^>]+)>"); - // private static final Pattern CLASSNAME_MATCH_PATTERN = - // Pattern.compile("([a-zA-Z_][a-zA-Z_0-9]*(?:\\.[a-zA-Z_][a-zA-Z_0-9]*)*)"); - private static Pattern methodToFragmentConverter = Pattern.compile("(.+)\\.(.+)\\(.*\\)"); + private static final Pattern PACKAGE_REMOVAL_PATTERN = Pattern.compile("[a-zA-Z_][a-zA-Z_0-9]*\\."); - public static String typeForDocumentationNavigation(String type) { + private static final Pattern GENERIC_SECTION_REMOVAL_PATTERN = Pattern.compile("<(?[^>]+)>"); + + private static final Pattern methodToFragmentConverter = Pattern.compile("(.+)\\.(.+)\\(.*\\)"); + + public static String typeForDocumentationNavigation(final String type) { return type.replaceAll("\\$", "."); } - public static void updateClassNameAsJavadocHtml(StringBuilder buffer, String type) { - Matcher matcher = GENERIC_SECTION_REMOVAL_PATTERN.matcher(type); + public static void updateClassNameAsJavadocHtml(final StringBuilder buffer, final String type) { + final Matcher matcher = GENERIC_SECTION_REMOVAL_PATTERN.matcher(type); String baseClass = type; - boolean parametersPresent = matcher.find(); + + final boolean parametersPresent = matcher.find(); String[] typeParameters = null; + if (parametersPresent) { typeParameters = matcher.group("commaDelimitedTypes").split(","); - baseClass = matcher.replaceAll(""); + baseClass = matcher.replaceAll(StringUtils.EMPTY); } + createHyperlink(buffer, typeForDocumentationNavigation(baseClass), baseClass, false); if (typeParameters != null) { buffer.append("<"); + for (int i = 0; i < typeParameters.length; i++) { updateClassNameAsJavadocHtml(buffer, typeParameters[i]); + if (i != typeParameters.length - 1) { buffer.append(", "); } } + buffer.append(">"); } } - public static String methodForDocumentationNavigation(String typeAndMethod) { + public static String methodForDocumentationNavigation(final String typeAndMethod) { return methodToFragmentConverter.matcher(typeForDocumentationNavigation(typeAndMethod)) - .replaceAll("$1#$2"); + .replaceAll("$1#$2"); } @NotNull - public static String getCodeStyleIntent(InsertionContext insertionContext) { - final CodeStyleSettings currentSettings = - CodeStyleSettingsManager.getSettings(insertionContext.getProject()); + public static String getCodeStyleIntent(final InsertionContext insertionContext) { + final CodeStyleSettings currentSettings = CodeStyleSettingsManager.getSettings(insertionContext.getProject()); final CommonCodeStyleSettings.IndentOptions indentOptions = - currentSettings.getIndentOptions(insertionContext.getFile().getFileType()); - return indentOptions.USE_TAB_CHARACTER ? - "\t" : - StringUtil.repeatSymbol(' ', indentOptions.INDENT_SIZE); + currentSettings.getIndentOptions(insertionContext.getFile().getFileType()); + + return indentOptions.USE_TAB_CHARACTER ? "\t" : StringUtil.repeatSymbol(' ', indentOptions.INDENT_SIZE); } @NotNull public static String getFirstSentenceWithoutDot(String fullSentence) { if (containsChar(fullSentence, '.')) { - BreakIterator breakIterator = getSentenceInstance(Locale.US); + final BreakIterator breakIterator = getSentenceInstance(Locale.US); breakIterator.setText(fullSentence); fullSentence = fullSentence.substring(breakIterator.first(), breakIterator.next()).trim(); } if (isNotEmpty(fullSentence)) { - String withoutDot = endsWithChar(fullSentence, '.') ? - fullSentence.substring(0, fullSentence.length() - 1) : - fullSentence; - return replace(withoutDot, "\n", ""); - } else { - return ""; + final String withoutDot = endsWithChar(fullSentence, '.') ? + fullSentence.substring(0, fullSentence.length() - 1) : fullSentence; + + return replace(withoutDot, StringUtils.LF, StringUtils.EMPTY); } + + return StringUtils.EMPTY; } - public static String moduleNamesAsStrCommaDelimited(List newModules, - boolean includeProjectName) { + public static String moduleNamesAsStrCommaDelimited(final List newModules, final boolean includeProjectName) { return moduleNamesAsStrCommaDelimited(newModules.stream(), includeProjectName); } - public static String moduleNamesAsStrCommaDelimited(Module[] newModules, - boolean includeProjectName) { + public static String moduleNamesAsStrCommaDelimited(final Module[] newModules, final boolean includeProjectName) { return moduleNamesAsStrCommaDelimited(stream(newModules), includeProjectName); } - private static String moduleNamesAsStrCommaDelimited(Stream moduleStream, - boolean includeProjectName) { + private static String moduleNamesAsStrCommaDelimited(final Stream moduleStream, final boolean includeProjectName) { return moduleStream.map(module -> includeProjectName ? - module.getProject().getName() + ":" + module.getName() : - module.getName()).collect(joining(", ")); + module.getProject().getName() + ":" + module.getName() : + module.getName()).collect(joining(", ")); } - public static String truncateIdeaDummyIdentifier(@NotNull PsiElement element) { + public static String truncateIdeaDummyIdentifier(@NotNull final PsiElement element) { return truncateIdeaDummyIdentifier(element.getText()); } - public static String truncateIdeaDummyIdentifier(String text) { - return text.replace(DUMMY_IDENTIFIER_TRIMMED, ""); + public static String truncateIdeaDummyIdentifier(final String text) { + return text.replace(DUMMY_IDENTIFIER_TRIMMED, StringUtils.EMPTY); } @SafeVarargs - public static List modifiableList(T... items) { + public static List modifiableList(final T... items) { return new ArrayList<>(asList(items)); } - public static List newListWithMembers(List itemsToCopy, T newItem) { - ArrayList newModifiableList = new ArrayList<>(itemsToCopy); + public static List newListWithMembers(final List itemsToCopy, final T newItem) { + final ArrayList newModifiableList = new ArrayList<>(itemsToCopy); newModifiableList.add(newItem); return newModifiableList; } - public static String removeGenerics(String type) { - Matcher matcher = GENERIC_SECTION_REMOVAL_PATTERN.matcher(type); + public static String removeGenerics(final String type) { + final Matcher matcher = GENERIC_SECTION_REMOVAL_PATTERN.matcher(type); if (matcher.find()) { - return matcher.replaceAll(""); + return matcher.replaceAll(StringUtils.EMPTY); } return type; } - public static String shortenedType(String type) { + public static String shortenedType(final String type) { if (type == null) { return null; } - Matcher matcher = PACKAGE_REMOVAL_PATTERN.matcher(type); + final Matcher matcher = PACKAGE_REMOVAL_PATTERN.matcher(type); if (matcher.find()) { - return matcher.replaceAll(""); + return matcher.replaceAll(StringUtils.EMPTY); } return type; } public static String dotDelimitedOriginalNames( - List matchesTopFirstTillParentNode, SuggestionNode currentNode) { - StringBuilder builder = new StringBuilder(); + final List matchesTopFirstTillParentNode, final SuggestionNode currentNode) { + final StringBuilder builder = new StringBuilder(); - for (SuggestionNode aMatchesTopFirstTillParentNode : matchesTopFirstTillParentNode) { - String originalName = aMatchesTopFirstTillParentNode.getOriginalName(); + for (final SuggestionNode aMatchesTopFirstTillParentNode : matchesTopFirstTillParentNode) { + final String originalName = aMatchesTopFirstTillParentNode.getOriginalName(); if (originalName != null) { builder.append(originalName).append("."); } } - String originalName = currentNode.getOriginalName(); + final String originalName = currentNode.getOriginalName(); if (originalName != null) { builder.append(originalName); } return builder.toString(); } - public static String dotDelimitedOriginalNames(List matches) { + public static String dotDelimitedOriginalNames(final List matches) { return dotDelimitedOriginalNames(matches, 0); } - public static String dotDelimitedOriginalNames(List matches, - int startIndex) { - StringBuilder builder = new StringBuilder(); + public static String dotDelimitedOriginalNames(final List matches, final int startIndex) { + final StringBuilder builder = new StringBuilder(); for (int i = startIndex; i < matches.size(); i++) { - String originalName = matches.get(i).getOriginalName(); + final String originalName = matches.get(i).getOriginalName(); if (originalName != null) { builder.append(originalName); - boolean appendDot = i < matches.size() - 1; + final boolean appendDot = i < matches.size() - 1; if (appendDot) { builder.append("."); } @@ -200,33 +199,30 @@ public static String dotDelimitedOriginalNames(List ma } @NotNull - public static String getIndent(String indent, int numOfHops) { + public static String getIndent(final String indent, final int numOfHops) { if (numOfHops == 0) { - return ""; - } - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < numOfHops; i++) { - builder.append(indent); + return StringUtils.EMPTY; } - return builder.toString(); + + return String.valueOf(indent).repeat(Math.max(0, numOfHops)); } @NotNull - public static String getOverallIndent(String existingIndentation, String indentPerLevel, - int numOfLevels) { + public static String getOverallIndent(final String existingIndentation, final String indentPerLevel, final int numOfLevels) { return existingIndentation + getIndent(indentPerLevel, numOfLevels); } @NotNull - public static > SortedSet newSingleElementSortedSet(T t) { - SortedSet suggestions = new TreeSet<>(); + public static > SortedSet newSingleElementSortedSet(final T t) { + final SortedSet suggestions = new TreeSet<>(); suggestions.add(t); + return suggestions; } public static Optional getKeyNameOfObject(final PsiElement psiElement) { return Optional.of(psiElement).filter(el -> el instanceof YAMLKeyValue) - .map(YAMLKeyValue.class::cast).map(YAMLKeyValue::getName); + .map(YAMLKeyValue.class::cast).map(YAMLKeyValue::getName); } } diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/misc/PsiCustomUtil.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/misc/PsiCustomUtil.java index 4a89edd..0899818 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/misc/PsiCustomUtil.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/misc/PsiCustomUtil.java @@ -22,6 +22,7 @@ import com.intellij.psi.PsiType; import com.intellij.psi.PsiTypeParameter; import com.intellij.psi.PsiWildcardType; +import com.intellij.psi.impl.source.PsiClassReferenceType; import com.intellij.psi.javadoc.PsiDocComment; import com.intellij.psi.util.CachedValue; import com.intellij.psi.util.PropertyUtil; @@ -33,6 +34,7 @@ import in.oneton.idea.spring.assistant.plugin.suggestion.SuggestionNodeType; import in.oneton.idea.spring.assistant.plugin.suggestion.clazz.GenericClassMemberWrapper; import lombok.experimental.UtilityClass; +import org.gradle.internal.impldep.org.junit.platform.commons.function.Try; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; @@ -58,7 +60,7 @@ import static com.intellij.psi.util.PropertyUtil.getPropertyName; import static com.intellij.psi.util.PropertyUtil.isSimplePropertyGetter; import static com.intellij.psi.util.PropertyUtil.isSimplePropertySetter; -import static com.intellij.psi.util.PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT; +import static com.intellij.psi.util.PsiModificationTracker.MODIFICATION_COUNT; import static com.intellij.psi.util.PsiTypesUtil.getClassType; import static com.intellij.psi.util.PsiTypesUtil.hasUnresolvedComponents; import static com.intellij.psi.util.PsiUtil.extractIterableTypeParameter; @@ -84,561 +86,568 @@ @UtilityClass public class PsiCustomUtil { - private static final Logger log = Logger.getInstance(PsiCustomUtil.class); - - private static final Key>> - SPRING_ASSISTANT_PLUGIN_PROPERTY_TO_CLASS_MEMBER_WRAPPER_KEY = - create("spring_assistant_plugin_property_to_class_member_wrapper"); - private static final Key> - SPRING_ASSISTANT_PLUGIN_ERASE_FREE_TYPE_PARAMETER_TYPE_KEY = - create("spring_assistant_plugin_eraseFreeTypeParameterType"); - private static final Key> SPRING_ASSISTANT_PLUGIN_FIRST_PARAMETER_TYPE_KEY = - create("spring_assistant_plugin_firstParameterType"); - private static final Key> SPRING_ASSISTANT_PLUGIN_RETURN_TYPE_KEY = - create("spring_assistant_plugin_returnType"); - - @Nullable - public static PsiType safeGetValidType(@NotNull Module module, @NotNull String fqn) { - try { - // Intellij expects inner classes to be referred via `.` instead of `$` - PsiType type = JavaPsiFacade.getInstance(module.getProject()).getParserFacade() - .createTypeFromText(fqn.replaceAll("\\$", "."), null); - boolean typeValid = isValidType(type); - if (typeValid) { - if (type instanceof PsiClassType) { - return PsiClassType.class.cast(type); + private static final Logger log = Logger.getInstance(PsiCustomUtil.class); + + private static final Key>> + SPRING_ASSISTANT_PLUGIN_PROPERTY_TO_CLASS_MEMBER_WRAPPER_KEY = + create("spring_assistant_plugin_property_to_class_member_wrapper"); + private static final Key> + SPRING_ASSISTANT_PLUGIN_ERASE_FREE_TYPE_PARAMETER_TYPE_KEY = + create("spring_assistant_plugin_eraseFreeTypeParameterType"); + private static final Key> SPRING_ASSISTANT_PLUGIN_FIRST_PARAMETER_TYPE_KEY = + create("spring_assistant_plugin_firstParameterType"); + private static final Key> SPRING_ASSISTANT_PLUGIN_RETURN_TYPE_KEY = + create("spring_assistant_plugin_returnType"); + + @Nullable + public static PsiType safeGetValidType(@NotNull Module module, @NotNull String fqn) { + try { + // Intellij expects inner classes to be referred via `.` instead of `$` + PsiType type = JavaPsiFacade.getInstance(module.getProject()).getParserFacade() + .createTypeFromText(fqn.replaceAll("\\$", "."), null); + boolean typeValid = isValidType(type); + if (typeValid) { + if (type instanceof PsiClassType) { + return PsiClassType.class.cast(type); + } else if (type instanceof PsiArrayType) { + return PsiArrayType.class.cast(type); + } + } + return null; + } catch (IncorrectOperationException e) { + debug(() -> log.debug("Unable to find class fqn " + fqn)); + return null; + } + } + + @NotNull + public static PsiType getReferredPsiType(PsiElement psiElement) { + if (psiElement instanceof PsiField) { + return ((PsiField) psiElement).getType(); + } else if (psiElement instanceof PsiMethod) { + return requireNonNull(((PsiMethod) psiElement).getReturnType()); + } else if (psiElement instanceof PsiClass) { + return getClassType((PsiClass) psiElement); + } + throw new RuntimeException( + "Method supports psiElement of type PsiField, PsiMethod & PsiClass only"); + } + + @Nullable + public static Map getTypeParameters(@NotNull PsiElement psiElement) { + PsiType psiType = getReferredPsiType(psiElement); + return getTypeParameters(psiType); + } + + @Nullable + public static Map getTypeParameters(PsiType type) { + if (type instanceof PsiArrayType) { + return getTypeParameters(((PsiArrayType) type).getComponentType()); + } else if (type instanceof PsiPrimitiveType) { + return null; + } else if (type instanceof PsiClassType) { + PsiClassType.ClassResolveResult resolveResult = + PsiClassType.class.cast(type).resolveGenerics(); + if (resolveResult.isValidResult()) { + return resolveResult.getSubstitutor().getSubstitutionMap(); + } + } + return null; + } + + @NotNull + public static SuggestionNodeType getSuggestionNodeType(PsiType type) { + if (type == null) { + return UNDEFINED; } else if (type instanceof PsiArrayType) { - return PsiArrayType.class.cast(type); + return ARRAY; + } else if (type instanceof PsiPrimitiveType) { + SuggestionNodeType nodeType = getSuggestionNodeTypeForPrimitive(type); + return nodeType != null ? nodeType : UNKNOWN_CLASS; + } else if (type instanceof PsiClassType) { + SuggestionNodeType nodeType = getSuggestionNodeTypeForPrimitive(type); + if (nodeType != null) { + return nodeType; + } else if (type.getCanonicalText().equals(JAVA_LANG_STRING)) { + return STRING; + } + + // TODO: Need to check if this is required or not? + PsiClassType psiClassType = (PsiClassType) type; + PsiClassType.ClassResolveResult classResolveResult = psiClassType.resolveGenerics(); + if (classResolveResult.isValidResult()) { + PsiClass psiClass = requireNonNull(classResolveResult.getElement()); + if (psiClass.isEnum()) { + return ENUM; + } else if ("java.math.BigDecimal".equals(psiClass.getQualifiedName())) { + return DOUBLE; + } else if ("java.nio.charset.Charset".equals(psiClass.getQualifiedName()) + // charset is a string + || "org.springframework.http.MediaType".equals(psiClass.getQualifiedName()) + || "java.net.InetAddress".equals(psiClass.getQualifiedName()) // ip address or hostname + || "java.net.URI".equals(psiClass.getQualifiedName()) // url + || requireNonNull(psiClass.getQualifiedName()).startsWith("java.lang.Class") + // expecting class name + || "org.springframework.core.io.Resource".equals(psiClass.getQualifiedName()) + // spring resource such as classpath://myfile.json, file:///opt/myfile.json + || "java.util.Date".equals(psiClass.getQualifiedName()) || "java.sql.Date" + .equals(psiClass.getQualifiedName()) || "java.time.LocalDate" + .equals(psiClass.getQualifiedName()) || "java.time.LocalTime" + .equals(psiClass.getQualifiedName()) || "java.time.LocalDateTime" + .equals(psiClass.getQualifiedName()) || "java.time.ZoneId" + .equals(psiClass.getQualifiedName()) || "java.time.ZonedDateTime" + .equals(psiClass.getQualifiedName()) || "java.time.Instant" + .equals(psiClass.getQualifiedName()) || "java.time.Duration" + .equals(psiClass.getQualifiedName())// date & duation + || "java.time.Period".equals(psiClass.getQualifiedName()) || "java.util.Locale" + .equals(psiClass.getQualifiedName())) { + return STRING; + } else if (isMap(psiClass)) { + return MAP; + } else if (isIterable(psiClass)) { + return ITERABLE; + } else { + return KNOWN_CLASS; + } + } } - } - return null; - } catch (IncorrectOperationException e) { - debug(() -> log.debug("Unable to find class fqn " + fqn)); - return null; - } - } - - @NotNull - public static PsiType getReferredPsiType(PsiElement psiElement) { - if (psiElement instanceof PsiField) { - return ((PsiField) psiElement).getType(); - } else if (psiElement instanceof PsiMethod) { - return requireNonNull(((PsiMethod) psiElement).getReturnType()); - } else if (psiElement instanceof PsiClass) { - return getClassType((PsiClass) psiElement); - } - throw new RuntimeException( - "Method supports psiElement of type PsiField, PsiMethod & PsiClass only"); - } - - @Nullable - public static Map getTypeParameters(@NotNull PsiElement psiElement) { - PsiType psiType = getReferredPsiType(psiElement); - return getTypeParameters(psiType); - } - - @Nullable - public static Map getTypeParameters(PsiType type) { - if (type instanceof PsiArrayType) { - return getTypeParameters(((PsiArrayType) type).getComponentType()); - } else if (type instanceof PsiPrimitiveType) { - return null; - } else if (type instanceof PsiClassType) { - PsiClassType.ClassResolveResult resolveResult = - PsiClassType.class.cast(type).resolveGenerics(); - if (resolveResult.isValidResult()) { - return resolveResult.getSubstitutor().getSubstitutionMap(); - } - } - return null; - } - - @NotNull - public static SuggestionNodeType getSuggestionNodeType(PsiType type) { - if (type == null) { - return UNDEFINED; - } else if (type instanceof PsiArrayType) { - return ARRAY; - } else if (type instanceof PsiPrimitiveType) { - SuggestionNodeType nodeType = getSuggestionNodeTypeForPrimitive(type); - return nodeType != null ? nodeType : UNKNOWN_CLASS; - } else if (type instanceof PsiClassType) { - SuggestionNodeType nodeType = getSuggestionNodeTypeForPrimitive(type); - if (nodeType != null) { - return nodeType; - } else if (type.getCanonicalText().equals(JAVA_LANG_STRING)) { - return STRING; - } - - // TODO: Need to check if this is required or not? - PsiClassType psiClassType = (PsiClassType) type; - PsiClassType.ClassResolveResult classResolveResult = psiClassType.resolveGenerics(); - if (classResolveResult.isValidResult()) { - PsiClass psiClass = requireNonNull(classResolveResult.getElement()); - if (psiClass.isEnum()) { - return ENUM; - } else if ("java.math.BigDecimal".equals(psiClass.getQualifiedName())) { - return DOUBLE; - } else if ("java.nio.charset.Charset".equals(psiClass.getQualifiedName()) - // charset is a string - || "org.springframework.http.MediaType".equals(psiClass.getQualifiedName()) - || "java.net.InetAddress".equals(psiClass.getQualifiedName()) // ip address or hostname - || "java.net.URI".equals(psiClass.getQualifiedName()) // url - || requireNonNull(psiClass.getQualifiedName()).startsWith("java.lang.Class") - // expecting class name - || "org.springframework.core.io.Resource".equals(psiClass.getQualifiedName()) - // spring resource such as classpath://myfile.json, file:///opt/myfile.json - || "java.util.Date".equals(psiClass.getQualifiedName()) || "java.sql.Date" - .equals(psiClass.getQualifiedName()) || "java.time.LocalDate" - .equals(psiClass.getQualifiedName()) || "java.time.LocalTime" - .equals(psiClass.getQualifiedName()) || "java.time.LocalDateTime" - .equals(psiClass.getQualifiedName()) || "java.time.ZoneId" - .equals(psiClass.getQualifiedName()) || "java.time.ZonedDateTime" - .equals(psiClass.getQualifiedName()) || "java.time.Instant" - .equals(psiClass.getQualifiedName()) || "java.time.Duration" - .equals(psiClass.getQualifiedName())// date & duation - || "java.time.Period".equals(psiClass.getQualifiedName()) || "java.util.Locale" - .equals(psiClass.getQualifiedName())) { - return STRING; - } else if (isMap(psiClass)) { - return MAP; - } else if (isIterable(psiClass)) { - return ITERABLE; - } else { - return KNOWN_CLASS; + return UNKNOWN_CLASS; + } + + @Nullable + private static SuggestionNodeType getSuggestionNodeTypeForPrimitive(PsiType type) { + if (PsiType.BOOLEAN.equals(type) || PsiType.BOOLEAN + .equals(PsiPrimitiveType.getUnboxedType(type))) { + return BOOLEAN; + } else if (PsiType.BYTE.equals(type) || PsiType.BYTE + .equals(PsiPrimitiveType.getUnboxedType(type))) { + return BYTE; + } else if (PsiType.SHORT.equals(type) || PsiType.SHORT + .equals(PsiPrimitiveType.getUnboxedType(type))) { + return SHORT; + } else if (PsiType.INT.equals(type) || PsiType.INT + .equals(PsiPrimitiveType.getUnboxedType(type))) { + return INT; + } else if (PsiType.LONG.equals(type) || PsiType.LONG + .equals(PsiPrimitiveType.getUnboxedType(type))) { + return LONG; + } else if (PsiType.FLOAT.equals(type) || PsiType.FLOAT + .equals(PsiPrimitiveType.getUnboxedType(type))) { + return FLOAT; + } else if (PsiType.DOUBLE.equals(type) || PsiType.DOUBLE + .equals(PsiPrimitiveType.getUnboxedType(type))) { + return DOUBLE; + } else if (PsiType.CHAR.equals(type) || PsiType.CHAR + .equals(PsiPrimitiveType.getUnboxedType(type))) { + return CHAR; + } + return null; + } + + @Nullable + public static String toClassFqn(@NotNull PsiType type) { + if (type instanceof PsiArrayType) { + String componentLongName = toClassFqn(PsiArrayType.class.cast(type).getComponentType()); + if (componentLongName != null) { + return componentLongName + "[]"; + } + } else if (type instanceof PsiPrimitiveType) { + return type.getPresentableText(); + } else if (type instanceof PsiClassType) { + return type.getCanonicalText(); + } + return null; + } + + @Nullable + public static String toClassNonQualifiedName(@NotNull PsiType type) { + if (type instanceof PsiArrayType) { + String componentLongName = + toClassNonQualifiedName(PsiArrayType.class.cast(type).getComponentType()); + if (componentLongName != null) { + return componentLongName + "[]"; + } + } else if (type instanceof PsiPrimitiveType) { + return type.getPresentableText(); + } else if (type instanceof PsiClassType) { + return ((PsiClassType) type).getClassName(); + } + return null; + } + + private static boolean isMap(@NotNull PsiClass psiClass) { + return isClassSameOrDescendantOf(psiClass, JAVA_UTIL_MAP) || isClassSameOrDescendantOf(psiClass, + "java.util.Hashtable"); + } + + private static boolean isIterable(@NotNull PsiClass psiClass) { + return isClassSameOrDescendantOf(psiClass, JAVA_LANG_ITERABLE); + } + + private static boolean isClassSameOrDescendantOf(@NotNull PsiClass psiClass, + String expectedClassFqn) { + return psiClass.getQualifiedName() != null && isInheritor(psiClass, expectedClassFqn); + } + + @NotNull + public static PsiType getBoxedTypeFromPrimitiveType(Module module, + PsiPrimitiveType primitiveType) { + PsiType boxedPrimitiveType = safeGetValidType(module, primitiveType.getBoxedTypeName()); + assert boxedPrimitiveType instanceof PsiClassType; + return boxedPrimitiveType; + } + + @Nullable + public static String typeToFqn(Module module, @NotNull PsiType type) { + if (isValidType(type)) { + if (type instanceof PsiArrayType) { + type = PsiArrayType.class.cast(type).getComponentType(); + return type.getCanonicalText(); + } else if (type instanceof PsiPrimitiveType) { + return getBoxedTypeFromPrimitiveType(module, (PsiPrimitiveType) type).getCanonicalText(); + } else if (type instanceof PsiClassType) { + return type.getCanonicalText(); + } + } + return null; + } + + @Nullable + public static Set computeDependencies(Module module, @NotNull PsiType type) { + PsiType originalType = type; + if (isValidType(type)) { + if (type instanceof PsiArrayType) { + return computeDependencies(module, PsiArrayType.class.cast(type).getComponentType()); + } else if (type instanceof PsiPrimitiveType) { + type = getBoxedTypeFromPrimitiveType(module, (PsiPrimitiveType) type); + } else if (type instanceof PsiWildcardType) { + type = ((PsiWildcardType) type).getBound(); + } else if (type instanceof PsiCapturedWildcardType) { + PsiType lowerBound = ((PsiCapturedWildcardType) type).getLowerBound(); + type = (lowerBound != NULL ? lowerBound : ((PsiCapturedWildcardType) type).getUpperBound()); + } + + if (type instanceof PsiClassType) { + PsiClassType classType = (PsiClassType) type; + Collection typeParams = + classType.resolveGenerics().getSubstitutor().getSubstitutionMap().values(); + TObjectHashingStrategy nameComparingHashingStrategy = + new TObjectHashingStrategy() { + @Override + public int computeHashCode(PsiClass psiClass) { + return requireNonNull(psiClass.getQualifiedName()).hashCode(); + } + + @Override + public boolean equals(PsiClass psiClass, PsiClass other) { + return psiClass.hashCode() == other.hashCode(); + } + }; + Set dependencies = new THashSet<>(nameComparingHashingStrategy); + dependencies.add(toValidPsiClass(classType)); + for (PsiType typeParam : typeParams) { + if (typeParam + != null) { // if the user specified raw class such as Map instead of Map + Set childDependencies = computeDependencies(module, typeParam); + if (childDependencies == null) { + return null; + } + dependencies.addAll(childDependencies); + } + } + return dependencies; + } + + throw new IllegalAccessError( + "Only supports PsiArrayType, PsiPrimitiveType, PsiWildcardType, PsiCapturedWildcardType & PsiClassType. Does not support type: " + + originalType.getCanonicalText()); + } + return null; + } + + @Nullable + public static PsiClass toValidPsiClass(@NotNull PsiClassType type) { + if (isValidType(type)) { + return type.resolve(); } - } - } - return UNKNOWN_CLASS; - } - - @Nullable - private static SuggestionNodeType getSuggestionNodeTypeForPrimitive(PsiType type) { - if (PsiType.BOOLEAN.equals(type) || PsiType.BOOLEAN - .equals(PsiPrimitiveType.getUnboxedType(type))) { - return BOOLEAN; - } else if (PsiType.BYTE.equals(type) || PsiType.BYTE - .equals(PsiPrimitiveType.getUnboxedType(type))) { - return BYTE; - } else if (PsiType.SHORT.equals(type) || PsiType.SHORT - .equals(PsiPrimitiveType.getUnboxedType(type))) { - return SHORT; - } else if (PsiType.INT.equals(type) || PsiType.INT - .equals(PsiPrimitiveType.getUnboxedType(type))) { - return INT; - } else if (PsiType.LONG.equals(type) || PsiType.LONG - .equals(PsiPrimitiveType.getUnboxedType(type))) { - return LONG; - } else if (PsiType.FLOAT.equals(type) || PsiType.FLOAT - .equals(PsiPrimitiveType.getUnboxedType(type))) { - return FLOAT; - } else if (PsiType.DOUBLE.equals(type) || PsiType.DOUBLE - .equals(PsiPrimitiveType.getUnboxedType(type))) { - return DOUBLE; - } else if (PsiType.CHAR.equals(type) || PsiType.CHAR - .equals(PsiPrimitiveType.getUnboxedType(type))) { - return CHAR; - } - return null; - } - - @Nullable - public static String toClassFqn(@NotNull PsiType type) { - if (type instanceof PsiArrayType) { - String componentLongName = toClassFqn(PsiArrayType.class.cast(type).getComponentType()); - if (componentLongName != null) { - return componentLongName + "[]"; - } - } else if (type instanceof PsiPrimitiveType) { - return type.getPresentableText(); - } else if (type instanceof PsiClassType) { - return type.getCanonicalText(); - } - return null; - } - - @Nullable - public static String toClassNonQualifiedName(@NotNull PsiType type) { - if (type instanceof PsiArrayType) { - String componentLongName = - toClassNonQualifiedName(PsiArrayType.class.cast(type).getComponentType()); - if (componentLongName != null) { - return componentLongName + "[]"; - } - } else if (type instanceof PsiPrimitiveType) { - return type.getPresentableText(); - } else if (type instanceof PsiClassType) { - return ((PsiClassType) type).getClassName(); - } - return null; - } - - private static boolean isMap(@NotNull PsiClass psiClass) { - return isClassSameOrDescendantOf(psiClass, JAVA_UTIL_MAP) || isClassSameOrDescendantOf(psiClass, - "java.util.Hashtable"); - } - - private static boolean isIterable(@NotNull PsiClass psiClass) { - return isClassSameOrDescendantOf(psiClass, JAVA_LANG_ITERABLE); - } - - private static boolean isClassSameOrDescendantOf(@NotNull PsiClass psiClass, - String expectedClassFqn) { - return psiClass.getQualifiedName() != null && isInheritor(psiClass, expectedClassFqn); - } - - @NotNull - public static PsiType getBoxedTypeFromPrimitiveType(Module module, - PsiPrimitiveType primitiveType) { - PsiType boxedPrimitiveType = safeGetValidType(module, primitiveType.getBoxedTypeName()); - assert boxedPrimitiveType instanceof PsiClassType; - return boxedPrimitiveType; - } - - @Nullable - public static String typeToFqn(Module module, @NotNull PsiType type) { - if (isValidType(type)) { - if (type instanceof PsiArrayType) { - type = PsiArrayType.class.cast(type).getComponentType(); - return type.getCanonicalText(); - } else if (type instanceof PsiPrimitiveType) { - return getBoxedTypeFromPrimitiveType(module, (PsiPrimitiveType) type).getCanonicalText(); - } else if (type instanceof PsiClassType) { - return type.getCanonicalText(); - } - } - return null; - } - - @Nullable - public static Set computeDependencies(Module module, @NotNull PsiType type) { - PsiType originalType = type; - if (isValidType(type)) { - if (type instanceof PsiArrayType) { - return computeDependencies(module, PsiArrayType.class.cast(type).getComponentType()); - } else if (type instanceof PsiPrimitiveType) { - type = getBoxedTypeFromPrimitiveType(module, (PsiPrimitiveType) type); - } else if (type instanceof PsiWildcardType) { - type = ((PsiWildcardType) type).getBound(); - } else if (type instanceof PsiCapturedWildcardType) { - PsiType lowerBound = ((PsiCapturedWildcardType) type).getLowerBound(); - type = (lowerBound != NULL ? lowerBound : ((PsiCapturedWildcardType) type).getUpperBound()); - } - - if (type instanceof PsiClassType) { - PsiClassType classType = (PsiClassType) type; - Collection typeParams = - classType.resolveGenerics().getSubstitutor().getSubstitutionMap().values(); - TObjectHashingStrategy nameComparingHashingStrategy = - new TObjectHashingStrategy() { - @Override - public int computeHashCode(PsiClass psiClass) { - return requireNonNull(psiClass.getQualifiedName()).hashCode(); - } - - @Override - public boolean equals(PsiClass psiClass, PsiClass other) { - return psiClass.hashCode() == other.hashCode(); - } - }; - Set dependencies = new THashSet<>(nameComparingHashingStrategy); - dependencies.add(toValidPsiClass(classType)); - for (PsiType typeParam : typeParams) { - if (typeParam - != null) { // if the user specified raw class such as Map instead of Map - Set childDependencies = computeDependencies(module, typeParam); - if (childDependencies == null) { - return null; + return null; + } + + // Copied & modified from PsiUtil.ensureValidType + public static boolean isValidType(@NotNull PsiType type) { + if (!type.isValid()) { + TimeoutUtil.sleep(1); // to see if processing in another thread suddenly makes the type valid again (which is a bug) + if (!type.isValid()) { + return false; } - dependencies.addAll(childDependencies); - } } - return dependencies; - } - - throw new IllegalAccessError( - "Only supports PsiArrayType, PsiPrimitiveType, PsiWildcardType, PsiCapturedWildcardType & PsiClassType. Does not support type: " - + originalType.getCanonicalText()); - } - return null; - } - - @Nullable - public static PsiClass toValidPsiClass(@NotNull PsiClassType type) { - if (isValidType(type)) { - return type.resolve(); - } - return null; - } - - // Copied & modified from PsiUtil.ensureValidType - public static boolean isValidType(@NotNull PsiType type) { - if (!type.isValid()) { - TimeoutUtil.sleep( - 1); // to see if processing in another thread suddenly makes the type valid again (which is a bug) - if (!type.isValid()) { - return false; - } - } - if (type instanceof PsiArrayType) { - return isValidType(PsiArrayType.class.cast(type).getComponentType()); - } else if (type instanceof PsiWildcardType) { - PsiType bound = ((PsiWildcardType) type).getBound(); - return bound != null && isValidType(bound); - } else if (type instanceof PsiCapturedWildcardType) { - PsiType lowerBound = ((PsiCapturedWildcardType) type).getLowerBound(); - type = (lowerBound != NULL ? lowerBound : ((PsiCapturedWildcardType) type).getUpperBound()); - return type != NULL && isValidType(type); - } else if (type instanceof PsiClassType) { - PsiClassType.ClassResolveResult classResolveResult = ((PsiClassType) type).resolveGenerics(); - return classResolveResult.isValidResult() && isValidElement( - requireNonNull(classResolveResult.getElement())) && !hasUnresolvedComponents(type); - } - return true; - } - - /** - * Checks if the element is valid. If not, throws {@link com.intellij.psi.PsiInvalidElementAccessException} with - * a meaningful message that points to the reasons why the element is not valid and may contain the stack trace - * when it was invalidated. - */ - // Copied & modified from PsiUtilCore.ensureValid - private static boolean isValidElement(@NotNull PsiElement element) { - if (!element.isValid()) { - TimeoutUtil.sleep( - 1); // to see if processing in another thread suddenly makes the element valid again (which is a bug) - return element.isValid(); - } - return true; - } - - @Nullable - public static PsiType getComponentType(@NotNull PsiType type) { - if (type instanceof PsiArrayType) { - return ((PsiArrayType) type).getComponentType(); - } - return extractIterableTypeParameter(type, true); - } - - @Contract("_, null->false") - private static boolean representsCollection(@NotNull PsiClass psiClass, @Nullable PsiType type) { - return type != null && getCollectionItemType(psiClass, type) != null; - } - - @Nullable - private static PsiType getCollectionItemType(@NotNull PsiClass psiClass, @NotNull PsiType type) { - return JavaGenericsUtil.getCollectionItemType(type, psiClass.getResolveScope()); - } - - @Nullable - public static Map getSanitisedPropertyToPsiMemberWrapper( - @Nullable PsiClass psiClass) { - if (psiClass != null) { - return getCachedValue(psiClass, SPRING_ASSISTANT_PLUGIN_PROPERTY_TO_CLASS_MEMBER_WRAPPER_KEY, - () -> create(prepareWritableProperties(psiClass), JAVA_STRUCTURE_MODIFICATION_COUNT)); - } - return null; - } - - @NotNull - private static Map prepareWritableProperties( - @NotNull PsiClass psiClass) { - final Map memberNameToMemberWrapper = new THashMap<>(); - for (PsiMethod method : psiClass.getAllMethods()) { - if (method.hasModifierProperty(STATIC) || !method.hasModifierProperty(PUBLIC)) { - continue; - } - if (isSimplePropertyGetter(method)) { - PsiMember acceptableMember = method; - final String propertyName = getPropertyName(method); - assert propertyName != null; - - PsiMethod setter = findInstancePropertySetter(psiClass, propertyName); - if (setter != null) { - final PsiType setterArgType = setter.getParameterList().getParameters()[0].getType(); - final PsiField field = psiClass.findFieldByName(propertyName, true); - if (field != null && !field.hasModifierProperty(STATIC)) { - final PsiType fieldType = getWritablePropertyType(psiClass, field); - if (fieldType == null || setterArgType.isConvertibleFrom(fieldType)) { - acceptableMember = field; + + if (type instanceof PsiArrayType) { + return isValidType(PsiArrayType.class.cast(type).getComponentType()); + } else if (type instanceof PsiWildcardType) { + PsiType bound = ((PsiWildcardType) type).getBound(); + + return bound != null && isValidType(bound); + } else if (type instanceof PsiCapturedWildcardType) { + PsiType lowerBound = ((PsiCapturedWildcardType) type).getLowerBound(); + type = (lowerBound != NULL ? lowerBound : ((PsiCapturedWildcardType) type).getUpperBound()); + + return type != NULL && isValidType(type); + } else if (type instanceof PsiClassType) { + final var psiClassType = (PsiClassType) type; + try { + var classResolveResult = psiClassType.resolveGenerics(); + return classResolveResult.isValidResult() && isValidElement(requireNonNull(classResolveResult.getElement())) && !hasUnresolvedComponents(type); + } catch (final com.intellij.openapi.project.IndexNotReadyException exception) { + return false; } - } + } + + return true; + } + + /** + * Checks if the element is valid. If not, throws {@link com.intellij.psi.PsiInvalidElementAccessException} with + * a meaningful message that points to the reasons why the element is not valid and may contain the stack trace + * when it was invalidated. + */ + // Copied & modified from PsiUtilCore.ensureValid + private static boolean isValidElement(@NotNull PsiElement element) { + if (!element.isValid()) { + TimeoutUtil.sleep( + 1); // to see if processing in another thread suddenly makes the element valid again (which is a bug) + return element.isValid(); + } + return true; + } + + @Nullable + public static PsiType getComponentType(@NotNull PsiType type) { + if (type instanceof PsiArrayType) { + return ((PsiArrayType) type).getComponentType(); + } + return extractIterableTypeParameter(type, true); + } + + @Contract("_, null->false") + private static boolean representsCollection(@NotNull PsiClass psiClass, @Nullable PsiType type) { + return type != null && getCollectionItemType(psiClass, type) != null; + } + + @Nullable + private static PsiType getCollectionItemType(@NotNull PsiClass psiClass, @NotNull PsiType type) { + return JavaGenericsUtil.getCollectionItemType(type, psiClass.getResolveScope()); + } + + @Nullable + public static Map getSanitisedPropertyToPsiMemberWrapper( + @Nullable PsiClass psiClass) { + if (psiClass != null) { + return getCachedValue(psiClass, SPRING_ASSISTANT_PLUGIN_PROPERTY_TO_CLASS_MEMBER_WRAPPER_KEY, + () -> create(prepareWritableProperties(psiClass), MODIFICATION_COUNT)); + } + return null; + } + + @NotNull + private static Map prepareWritableProperties( + @NotNull PsiClass psiClass) { + final Map memberNameToMemberWrapper = new THashMap<>(); + for (PsiMethod method : psiClass.getAllMethods()) { + if (method.hasModifierProperty(STATIC) || !method.hasModifierProperty(PUBLIC)) { + continue; + } + if (isSimplePropertyGetter(method)) { + PsiMember acceptableMember = method; + final String propertyName = getPropertyName(method); + assert propertyName != null; + + PsiMethod setter = findInstancePropertySetter(psiClass, propertyName); + if (setter != null) { + final PsiType setterArgType = setter.getParameterList().getParameters()[0].getType(); + final PsiField field = psiClass.findFieldByName(propertyName, true); + if (field != null && !field.hasModifierProperty(STATIC)) { + final PsiType fieldType = getWritablePropertyType(psiClass, field); + if (fieldType == null || setterArgType.isConvertibleFrom(fieldType)) { + acceptableMember = field; + } + } + } else { + final PsiType returnType = method.getReturnType(); + if (returnType != null && representsCollection(psiClass, returnType)) { + final PsiField field = psiClass.findFieldByName(propertyName, true); + if (field != null && !field.hasModifierProperty(STATIC)) { + final PsiType fieldType = getWritablePropertyType(psiClass, field); + if (fieldType == null || returnType.isAssignableFrom(fieldType)) { + acceptableMember = field; + } + } + } else { + acceptableMember = null; + } + } + if (acceptableMember != null) + memberNameToMemberWrapper + .put(sanitise(propertyName), new GenericClassMemberWrapper(acceptableMember)); + } + } + return memberNameToMemberWrapper; + } + + @Nullable + private static PsiType getWritablePropertyType(@Nullable PsiClass containingClass, + @Nullable PsiElement declaration) { + if (declaration instanceof PsiField) { + return getFieldType((PsiField) declaration); + } + if (declaration instanceof PsiMethod) { + final PsiMethod method = (PsiMethod) declaration; + if (method.getParameterList().getParametersCount() != 0) { + return getSetterArgumentType(method); + } + final String propertyName = getPropertyName(method); + final PsiClass psiClass = + containingClass != null ? containingClass : method.getContainingClass(); + if (propertyName != null && containingClass != null) { + final PsiMethod setter = findInstancePropertySetter(psiClass, propertyName); + if (setter != null) { + final PsiType setterArgumentType = getSetterArgumentType(setter); + if (setterArgumentType != null) + return setterArgumentType; + } + } + return getGetterReturnType(method); + } + return null; + } + + @Nullable + private static PsiType getFieldType(final PsiField field) { + return getCachedValue(field, SPRING_ASSISTANT_PLUGIN_ERASE_FREE_TYPE_PARAMETER_TYPE_KEY, () -> { + final PsiType fieldType = field.getType(); + final PsiClassType.ClassResolveResult resolveResult = resolveGenericsClassInType(fieldType); + final PsiClass fieldClass = resolveResult.getElement(); + if (fieldClass == null) { + final PsiType propertyType = eraseFreeTypeParameters(fieldType, field); + return create(propertyType, MODIFICATION_COUNT); + } + return null; + }); + } + + @Nullable + private static PsiType getSetterArgumentType(@NotNull PsiMethod method) { + return getCachedValue(method, SPRING_ASSISTANT_PLUGIN_FIRST_PARAMETER_TYPE_KEY, () -> { + final PsiParameter[] parameters = method.getParameterList().getParameters(); + if (!method.hasModifierProperty(STATIC) && parameters.length == 1) { + final PsiType argumentType = eraseFreeTypeParameters(parameters[0].getType(), method); + return create(argumentType, MODIFICATION_COUNT); + } + return create(null, MODIFICATION_COUNT); + }); + } + + @Nullable + private static PsiType eraseFreeTypeParameters(@Nullable PsiType psiType, + @NotNull PsiMember member) { + final PsiClass containingClass = member.getContainingClass(); + return eraseFreeTypeParameters(psiType, containingClass); + } + + @Nullable + private static PsiType eraseFreeTypeParameters(@Nullable PsiType psiType, + @Nullable PsiClass containingClass) { + if (containingClass == null) { + return null; + } + return getElementFactory(containingClass.getProject()).createRawSubstitutor(containingClass) + .substitute(psiType); + } + + private static PsiType getGetterReturnType(@NotNull PsiMethod method) { + return getCachedValue(method, SPRING_ASSISTANT_PLUGIN_RETURN_TYPE_KEY, () -> { + final PsiType returnType = eraseFreeTypeParameters(method.getReturnType(), method); + return create(returnType, MODIFICATION_COUNT); + }); + } + + @Nullable + private static PsiMethod findInstancePropertySetter(@NotNull PsiClass psiClass, + @Nullable String propertyName) { + if (StringUtil.isEmpty(propertyName)) + return null; + final String suggestedSetterName = PropertyUtil.suggestSetterName(propertyName); + final PsiMethod[] setters = psiClass.findMethodsByName(suggestedSetterName, true); + for (PsiMethod setter : setters) { + if (setter.hasModifierProperty(PUBLIC) && !setter.hasModifierProperty(STATIC) + && isSimplePropertySetter(setter)) { + return setter; + } + } + return null; + } + + @Nullable + public static Module findModule(@NotNull PsiElement element) { + return findModuleForPsiElement(element); + } + + @Nullable + public static Module findModule(@NotNull InsertionContext context) { + return findModuleForFile(context.getFile().getVirtualFile(), context.getProject()); + } + + @Nullable + public static String computeDocumentation(PsiMember member) { + PsiDocComment docComment; + if (member instanceof PsiField) { + docComment = PsiField.class.cast(member).getDocComment(); + } else if (member instanceof PsiMethod) { + docComment = PsiMethod.class.cast(member).getDocComment(); } else { - final PsiType returnType = method.getReturnType(); - if (returnType != null && representsCollection(psiClass, returnType)) { - final PsiField field = psiClass.findFieldByName(propertyName, true); - if (field != null && !field.hasModifierProperty(STATIC)) { - final PsiType fieldType = getWritablePropertyType(psiClass, field); - if (fieldType == null || returnType.isAssignableFrom(fieldType)) { - acceptableMember = field; - } + throw new RuntimeException("Method supports targets of type PsiField & PsiMethod only"); + } + if (docComment != null) { + StringBuilder builder = new StringBuilder(); + new JavaDocInfoGenerator(member.getProject(), member) + .generateCommonSection(builder, docComment); + return builder.toString().trim(); + } + return null; + } + + @Nullable + public static VirtualFile findFileUnderRootInModule(Module module, String targetFileName) { + VirtualFile[] contentRoots = getInstance(module).getContentRoots(); + for (VirtualFile contentRoot : contentRoots) { + VirtualFile childFile = findFileUnderRootInModule(contentRoot, targetFileName); + if (childFile != null) { + return childFile; } - } else { - acceptableMember = null; - } } - if (acceptableMember != null) - memberNameToMemberWrapper - .put(sanitise(propertyName), new GenericClassMemberWrapper(acceptableMember)); - } - } - return memberNameToMemberWrapper; - } - - @Nullable - private static PsiType getWritablePropertyType(@Nullable PsiClass containingClass, - @Nullable PsiElement declaration) { - if (declaration instanceof PsiField) { - return getFieldType((PsiField) declaration); - } - if (declaration instanceof PsiMethod) { - final PsiMethod method = (PsiMethod) declaration; - if (method.getParameterList().getParametersCount() != 0) { - return getSetterArgumentType(method); - } - final String propertyName = getPropertyName(method); - final PsiClass psiClass = - containingClass != null ? containingClass : method.getContainingClass(); - if (propertyName != null && containingClass != null) { - final PsiMethod setter = findInstancePropertySetter(psiClass, propertyName); - if (setter != null) { - final PsiType setterArgumentType = getSetterArgumentType(setter); - if (setterArgumentType != null) - return setterArgumentType; + return null; + } + + @Nullable + public static VirtualFile findFileUnderRootInModule(@NotNull VirtualFile contentRoot, + String targetFileName) { + VirtualFile childFile = contentRoot.findChild(targetFileName); + if (childFile != null) { + return childFile; + } + return null; + } + + /** + * Debug logging can be enabled by adding fully classified class name/package name with # prefix + * For eg., to enable debug logging, go `Help > Debug log settings` & type `#in.oneton.idea.spring.assistant.plugin.suggestion.service.SuggestionServiceImpl` + * + * @param doWhenDebug code to execute when debug is enabled + */ + private static void debug(Runnable doWhenDebug) { + if (log.isDebugEnabled()) { + doWhenDebug.run(); } - } - return getGetterReturnType(method); - } - return null; - } - - @Nullable - private static PsiType getFieldType(final PsiField field) { - return getCachedValue(field, SPRING_ASSISTANT_PLUGIN_ERASE_FREE_TYPE_PARAMETER_TYPE_KEY, () -> { - final PsiType fieldType = field.getType(); - final PsiClassType.ClassResolveResult resolveResult = resolveGenericsClassInType(fieldType); - final PsiClass fieldClass = resolveResult.getElement(); - if (fieldClass == null) { - final PsiType propertyType = eraseFreeTypeParameters(fieldType, field); - return create(propertyType, JAVA_STRUCTURE_MODIFICATION_COUNT); - } - return null; - }); - } - - @Nullable - private static PsiType getSetterArgumentType(@NotNull PsiMethod method) { - return getCachedValue(method, SPRING_ASSISTANT_PLUGIN_FIRST_PARAMETER_TYPE_KEY, () -> { - final PsiParameter[] parameters = method.getParameterList().getParameters(); - if (!method.hasModifierProperty(STATIC) && parameters.length == 1) { - final PsiType argumentType = eraseFreeTypeParameters(parameters[0].getType(), method); - return create(argumentType, JAVA_STRUCTURE_MODIFICATION_COUNT); - } - return create(null, JAVA_STRUCTURE_MODIFICATION_COUNT); - }); - } - - @Nullable - private static PsiType eraseFreeTypeParameters(@Nullable PsiType psiType, - @NotNull PsiMember member) { - final PsiClass containingClass = member.getContainingClass(); - return eraseFreeTypeParameters(psiType, containingClass); - } - - @Nullable - private static PsiType eraseFreeTypeParameters(@Nullable PsiType psiType, - @Nullable PsiClass containingClass) { - if (containingClass == null) { - return null; - } - return getElementFactory(containingClass.getProject()).createRawSubstitutor(containingClass) - .substitute(psiType); - } - - private static PsiType getGetterReturnType(@NotNull PsiMethod method) { - return getCachedValue(method, SPRING_ASSISTANT_PLUGIN_RETURN_TYPE_KEY, () -> { - final PsiType returnType = eraseFreeTypeParameters(method.getReturnType(), method); - return create(returnType, JAVA_STRUCTURE_MODIFICATION_COUNT); - }); - } - - @Nullable - private static PsiMethod findInstancePropertySetter(@NotNull PsiClass psiClass, - @Nullable String propertyName) { - if (StringUtil.isEmpty(propertyName)) - return null; - final String suggestedSetterName = PropertyUtil.suggestSetterName(propertyName); - final PsiMethod[] setters = psiClass.findMethodsByName(suggestedSetterName, true); - for (PsiMethod setter : setters) { - if (setter.hasModifierProperty(PUBLIC) && !setter.hasModifierProperty(STATIC) - && isSimplePropertySetter(setter)) { - return setter; - } - } - return null; - } - - @Nullable - public static Module findModule(@NotNull PsiElement element) { - return findModuleForPsiElement(element); - } - - @Nullable - public static Module findModule(@NotNull InsertionContext context) { - return findModuleForFile(context.getFile().getVirtualFile(), context.getProject()); - } - - @Nullable - public static String computeDocumentation(PsiMember member) { - PsiDocComment docComment; - if (member instanceof PsiField) { - docComment = PsiField.class.cast(member).getDocComment(); - } else if (member instanceof PsiMethod) { - docComment = PsiMethod.class.cast(member).getDocComment(); - } else { - throw new RuntimeException("Method supports targets of type PsiField & PsiMethod only"); - } - if (docComment != null) { - StringBuilder builder = new StringBuilder(); - new JavaDocInfoGenerator(member.getProject(), member) - .generateCommonSection(builder, docComment); - return builder.toString().trim(); - } - return null; - } - - @Nullable - public static VirtualFile findFileUnderRootInModule(Module module, String targetFileName) { - VirtualFile[] contentRoots = getInstance(module).getContentRoots(); - for (VirtualFile contentRoot : contentRoots) { - VirtualFile childFile = findFileUnderRootInModule(contentRoot, targetFileName); - if (childFile != null) { - return childFile; - } - } - return null; - } - - @Nullable - public static VirtualFile findFileUnderRootInModule(@NotNull VirtualFile contentRoot, - String targetFileName) { - VirtualFile childFile = contentRoot.findChild(targetFileName); - if (childFile != null) { - return childFile; - } - return null; - } - - /** - * Debug logging can be enabled by adding fully classified class name/package name with # prefix - * For eg., to enable debug logging, go `Help > Debug log settings` & type `#in.oneton.idea.spring.assistant.plugin.suggestion.service.SuggestionServiceImpl` - * - * @param doWhenDebug code to execute when debug is enabled - */ - private static void debug(Runnable doWhenDebug) { - if (log.isDebugEnabled()) { - doWhenDebug.run(); - } - } + } } diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/clazz/ClassMetadataProxy.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/clazz/ClassMetadataProxy.java index 0587d3e..7f07d07 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/clazz/ClassMetadataProxy.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/clazz/ClassMetadataProxy.java @@ -8,7 +8,6 @@ import com.intellij.psi.PsiType; import com.intellij.psi.util.CachedValue; import com.intellij.util.ConcurrencyUtil; -import com.intellij.util.containers.ContainerUtil; import in.oneton.idea.spring.assistant.plugin.suggestion.Suggestion; import in.oneton.idea.spring.assistant.plugin.suggestion.SuggestionNode; import in.oneton.idea.spring.assistant.plugin.suggestion.SuggestionNodeType; @@ -21,6 +20,7 @@ import java.util.List; import java.util.Set; import java.util.SortedSet; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import static com.intellij.psi.util.CachedValueProvider.Result.create; @@ -34,158 +34,161 @@ public class ClassMetadataProxy implements MetadataProxy { - private static final Logger log = Logger.getInstance(ClassMetadataProxy.class); - - private static ConcurrentMap>> fqnToKey = - ContainerUtil.newConcurrentMap(); - - @NotNull - private final PsiClass targetClass; - - @NotNull - private final PsiClassType type; - - ClassMetadataProxy(@NotNull PsiClassType type) { - this.type = type; - targetClass = requireNonNull(toValidPsiClass(type)); - } - - @Nullable - @Override - public SuggestionDocumentationHelper findDirectChild(Module module, String pathSegment) { - return doWithTargetAndReturn(module, target -> target.findDirectChild(module, pathSegment), - null); - } - - @Nullable - @Override - public Collection findDirectChildrenForQueryPrefix( - Module module, String querySegmentPrefix) { - return findDirectChildrenForQueryPrefix(module, querySegmentPrefix, null); - } - - @Nullable - @Override - public Collection findDirectChildrenForQueryPrefix( - Module module, String querySegmentPrefix, @Nullable Set siblingsToExclude) { - return doWithTargetAndReturn(module, target -> target - .findDirectChildrenForQueryPrefix(module, querySegmentPrefix, siblingsToExclude), null); - } - - @Nullable - @Override - public List findDeepestSuggestionNode(Module module, - List matchesRootTillParentNode, String[] pathSegments, - int pathSegmentStartIndex) { - return doWithTargetAndReturn(module, target -> target - .findDeepestSuggestionNode(module, matchesRootTillParentNode, pathSegments, - pathSegmentStartIndex), null); - } - - @Nullable - @Override - public SortedSet findKeySuggestionsForQueryPrefix(Module module, FileType fileType, - List matchesRootTillMe, int numOfAncestors, String[] querySegmentPrefixes, - int querySegmentPrefixStartIndex) { - return doWithTargetAndReturn(module, target -> target - .findKeySuggestionsForQueryPrefix(module, fileType, matchesRootTillMe, numOfAncestors, - querySegmentPrefixes, querySegmentPrefixStartIndex), null); - } - - @Nullable - @Override - public SortedSet findKeySuggestionsForQueryPrefix(Module module, FileType fileType, - List matchesRootTillMe, int numOfAncestors, String[] querySegmentPrefixes, - int querySegmentPrefixStartIndex, @Nullable Set siblingsToExclude) { - return doWithTargetAndReturn(module, target -> target - .findKeySuggestionsForQueryPrefix(module, fileType, matchesRootTillMe, numOfAncestors, - querySegmentPrefixes, querySegmentPrefixStartIndex, siblingsToExclude), null); - } - - @Nullable - @Override - public SortedSet findValueSuggestionsForPrefix(Module module, FileType fileType, - List matchesRootTillMe, String prefix) { - return findValueSuggestionsForPrefix(module, fileType, matchesRootTillMe, prefix, null); - } - - @Nullable - @Override - public SortedSet findValueSuggestionsForPrefix(Module module, FileType fileType, - List matchesRootTillMe, String prefix, - @Nullable Set siblingsToExclude) { - return doWithTargetAndReturn(module, target -> target - .findValueSuggestionsForPrefix(module, fileType, matchesRootTillMe, prefix, - siblingsToExclude), null); - } - - @Nullable - @Override - public String getDocumentationForValue(Module module, String nodeNavigationPathDotDelimited, - String originalValue) { - return doWithTargetAndReturn(module, - target -> target.getDocumentationForValue(module, nodeNavigationPathDotDelimited, - originalValue), - null); - } - - @Override - public boolean isLeaf(Module module) { - return doWithTargetAndReturn(module, target -> target.isLeaf(module), true); - } - - @NotNull - @Override - public SuggestionNodeType getSuggestionNodeType(Module module) { - return doWithTargetAndReturn(module, ClassMetadata::getSuggestionNodeType, UNKNOWN_CLASS); - } - - @Nullable - @Override - public PsiType getPsiType(Module module) { - return doWithTargetAndReturn(module, target -> target.getPsiType(module), null); - } - - @Override - public boolean targetRepresentsArray() { - return false; - } - - @Override - public boolean targetClassRepresentsIterable(Module module) { - return doWithTargetAndReturn(module, IterableClassMetadata.class::isInstance, false); - } - - T doWithTargetAndReturn(Module module, - TargetInvokerWithReturnValue targetInvokerWithReturnValue, T defaultReturnValue) { - ClassMetadata target = getTarget(module); - if (target != null) { - return targetInvokerWithReturnValue.invoke(target); - } - return defaultReturnValue; - } - - private ClassMetadata getTarget(Module module) { - String fqn = typeToFqn(module, type); - if (fqn != null) { - String userDataKeyRef = "spring_assistant_plugin_class_metadata:" + fqn; - Key> classMetadataKey = - ConcurrencyUtil.cacheOrGet(fqnToKey, userDataKeyRef, Key.create(userDataKeyRef)); - return getCachedValue(targetClass, classMetadataKey, () -> { - log.debug("Creating metadata instance for " + userDataKeyRef); - Set dependencies = computeDependencies(module, type); - if (dependencies != null) { - return create(newClassMetadata(type), dependencies); + private static final Logger log = Logger.getInstance(ClassMetadataProxy.class); + + private static final ConcurrentMap>> fqnToKey = new ConcurrentHashMap<>(); + + @NotNull + private final PsiClass targetClass; + + @NotNull + private final PsiClassType type; + + ClassMetadataProxy(@NotNull PsiClassType type) { + this.type = type; + targetClass = requireNonNull(toValidPsiClass(type)); + } + + @Nullable + @Override + public SuggestionDocumentationHelper findDirectChild(Module module, String pathSegment) { + return doWithTargetAndReturn(module, target -> target.findDirectChild(module, pathSegment), + null); + } + + @Nullable + @Override + public Collection findDirectChildrenForQueryPrefix( + Module module, String querySegmentPrefix) { + return findDirectChildrenForQueryPrefix(module, querySegmentPrefix, null); + } + + @Nullable + @Override + public Collection findDirectChildrenForQueryPrefix( + Module module, String querySegmentPrefix, @Nullable Set siblingsToExclude) { + return doWithTargetAndReturn(module, target -> target + .findDirectChildrenForQueryPrefix(module, querySegmentPrefix, siblingsToExclude), null); + } + + @Nullable + @Override + public List findDeepestSuggestionNode(Module module, + List matchesRootTillParentNode, String[] pathSegments, + int pathSegmentStartIndex) { + return doWithTargetAndReturn(module, target -> target + .findDeepestSuggestionNode(module, matchesRootTillParentNode, pathSegments, + pathSegmentStartIndex), null); + } + + @Nullable + @Override + public SortedSet findKeySuggestionsForQueryPrefix(Module module, FileType fileType, + List matchesRootTillMe, int numOfAncestors, String[] querySegmentPrefixes, + int querySegmentPrefixStartIndex) { + return doWithTargetAndReturn(module, target -> target + .findKeySuggestionsForQueryPrefix(module, fileType, matchesRootTillMe, numOfAncestors, + querySegmentPrefixes, querySegmentPrefixStartIndex), null); + } + + @Nullable + @Override + public SortedSet findKeySuggestionsForQueryPrefix(Module module, FileType fileType, + List matchesRootTillMe, int numOfAncestors, String[] querySegmentPrefixes, + int querySegmentPrefixStartIndex, @Nullable Set siblingsToExclude) { + return doWithTargetAndReturn(module, target -> target + .findKeySuggestionsForQueryPrefix(module, fileType, matchesRootTillMe, numOfAncestors, + querySegmentPrefixes, querySegmentPrefixStartIndex, siblingsToExclude), null); + } + + @Nullable + @Override + public SortedSet findValueSuggestionsForPrefix(Module module, FileType fileType, + List matchesRootTillMe, String prefix) { + return findValueSuggestionsForPrefix(module, fileType, matchesRootTillMe, prefix, null); + } + + @Nullable + @Override + public SortedSet findValueSuggestionsForPrefix(Module module, FileType fileType, + List matchesRootTillMe, String prefix, + @Nullable Set siblingsToExclude) { + return doWithTargetAndReturn(module, target -> target + .findValueSuggestionsForPrefix(module, fileType, matchesRootTillMe, prefix, + siblingsToExclude), null); + } + + @Nullable + @Override + public String getDocumentationForValue(Module module, String nodeNavigationPathDotDelimited, + String originalValue) { + return doWithTargetAndReturn(module, + target -> target.getDocumentationForValue(module, nodeNavigationPathDotDelimited, + originalValue), + null); + } + + @Override + public boolean isLeaf(Module module) { + return doWithTargetAndReturn(module, target -> target.isLeaf(module), true); + } + + @NotNull + @Override + public SuggestionNodeType getSuggestionNodeType(Module module) { + return doWithTargetAndReturn(module, ClassMetadata::getSuggestionNodeType, UNKNOWN_CLASS); + } + + @Nullable + @Override + public PsiType getPsiType(Module module) { + return doWithTargetAndReturn(module, target -> target.getPsiType(module), null); + } + + @Override + public boolean targetRepresentsArray() { + return false; + } + + @Override + public boolean targetClassRepresentsIterable(Module module) { + return doWithTargetAndReturn(module, IterableClassMetadata.class::isInstance, false); + } + + T doWithTargetAndReturn(Module module, TargetInvokerWithReturnValue targetInvokerWithReturnValue, T defaultReturnValue) { + ClassMetadata target = getTarget(module); + if (target != null) { + return targetInvokerWithReturnValue.invoke(target); + } + return defaultReturnValue; + } + + private ClassMetadata getTarget(Module module) { + final String fqn = typeToFqn(module, type); + + if (fqn == null) { + return null; } - return null; - }); + + final String userDataKeyRef = "spring_assistant_plugin_class_metadata:" + fqn; + Key> classMetadataKey = + ConcurrencyUtil.cacheOrGet(fqnToKey, userDataKeyRef, Key.create(userDataKeyRef)); + + return getCachedValue(targetClass, classMetadataKey, () -> { + log.debug("Creating metadata instance for " + userDataKeyRef); + final Set dependencies = computeDependencies(module, type); + + if (dependencies != null) { + return create(newClassMetadata(type), dependencies); + } + return null; + }); + } - return null; - } - protected interface TargetInvokerWithReturnValue { - T invoke(ClassMetadata classMetadata); - } + protected interface TargetInvokerWithReturnValue { + T invoke(ClassMetadata classMetadata); + } } diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/clazz/GenericClassMemberWrapper.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/clazz/GenericClassMemberWrapper.java index ffae59a..b7d42f3 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/clazz/GenericClassMemberWrapper.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/clazz/GenericClassMemberWrapper.java @@ -50,19 +50,19 @@ public GenericClassMemberWrapper(@NotNull PsiMember member) { this.originalName = requireNonNull(member.getName()); this.documentation = computeDocumentation(member); this.shortType = toClassNonQualifiedName(getReferredPsiType(member)); - this.deprecated = computeDeprecationStatus(); + this.deprecated = this.computeDeprecationStatus(); } public MetadataProxy getMemberReferredClassMetadataProxy(Module module) { - if (proxy == null) { - proxy = newMetadataProxy(module, getReferredPsiType(member)); + if (this.proxy == null) { + this.proxy = newMetadataProxy(module, getReferredPsiType(this.member)); } - return proxy; + return this.proxy; } private T doWithMemberReferredClassProxy(Module module, ProxyInvoker invoker, - T defaultValue) { - MetadataProxy delegate = getMemberReferredClassMetadataProxy(module); + T defaultValue) { + MetadataProxy delegate = this.getMemberReferredClassMetadataProxy(module); if (delegate != null) { return invoker.invoke(delegate); } @@ -72,70 +72,70 @@ private T doWithMemberReferredClassProxy(Module module, ProxyInvoker invo @Nullable @Override public List findDeepestSuggestionNode(Module module, - List matchesRootTillParentNode, String[] pathSegments, - int pathSegmentStartIndex) { - return doWithMemberReferredClassProxy(module, proxy -> proxy - .findDeepestSuggestionNode(module, matchesRootTillParentNode, pathSegments, - pathSegmentStartIndex), null); + List matchesRootTillParentNode, String[] pathSegments, + int pathSegmentStartIndex) { + return this.doWithMemberReferredClassProxy(module, proxy -> proxy + .findDeepestSuggestionNode(module, matchesRootTillParentNode, pathSegments, + pathSegmentStartIndex), null); } @Nullable @Override public SortedSet findKeySuggestionsForQueryPrefix(Module module, FileType fileType, - List matchesRootTillMe, int numOfAncestors, String[] querySegmentPrefixes, - int querySegmentPrefixStartIndex) { - return doWithMemberReferredClassProxy(module, proxy -> proxy - .findKeySuggestionsForQueryPrefix(module, fileType, matchesRootTillMe, numOfAncestors, - querySegmentPrefixes, querySegmentPrefixStartIndex), null); + List matchesRootTillMe, int numOfAncestors, String[] querySegmentPrefixes, + int querySegmentPrefixStartIndex) { + return this.doWithMemberReferredClassProxy(module, proxy -> proxy + .findKeySuggestionsForQueryPrefix(module, fileType, matchesRootTillMe, numOfAncestors, + querySegmentPrefixes, querySegmentPrefixStartIndex), null); } @Nullable @Override public SortedSet findKeySuggestionsForQueryPrefix(Module module, FileType fileType, - List matchesRootTillMe, int numOfAncestors, String[] querySegmentPrefixes, - int querySegmentPrefixStartIndex, @Nullable Set siblingsToExclude) { - return doWithMemberReferredClassProxy(module, proxy -> proxy - .findKeySuggestionsForQueryPrefix(module, fileType, matchesRootTillMe, numOfAncestors, - querySegmentPrefixes, querySegmentPrefixStartIndex, siblingsToExclude), null); + List matchesRootTillMe, int numOfAncestors, String[] querySegmentPrefixes, + int querySegmentPrefixStartIndex, @Nullable Set siblingsToExclude) { + return this.doWithMemberReferredClassProxy(module, proxy -> proxy + .findKeySuggestionsForQueryPrefix(module, fileType, matchesRootTillMe, numOfAncestors, + querySegmentPrefixes, querySegmentPrefixStartIndex, siblingsToExclude), null); } @NotNull public String getOriginalName() { - return originalName; + return this.originalName; } @Nullable @Override public String getNameForDocumentation(Module module) { - return doWithMemberReferredClassProxy(module, proxy -> { + return this.doWithMemberReferredClassProxy(module, proxy -> { if (proxy.targetRepresentsArray() || proxy.targetClassRepresentsIterable(module)) { - return originalName + "[]"; + return this.originalName + "[]"; } else { - return originalName; + return this.originalName; } - }, originalName); + }, this.originalName); } @Nullable @Override public SortedSet findValueSuggestionsForPrefix(Module module, FileType fileType, - List matchesRootTillMe, String prefix) { - return findValueSuggestionsForPrefix(module, fileType, matchesRootTillMe, prefix, null); + List matchesRootTillMe, String prefix) { + return this.findValueSuggestionsForPrefix(module, fileType, matchesRootTillMe, prefix, null); } @Nullable @Override public SortedSet findValueSuggestionsForPrefix(Module module, FileType fileType, - List matchesRootTillMe, String prefix, - @Nullable Set siblingsToExclude) { - return doWithMemberReferredClassProxy(module, proxy -> proxy - .findValueSuggestionsForPrefix(module, fileType, matchesRootTillMe, prefix, - siblingsToExclude), null); + List matchesRootTillMe, String prefix, + @Nullable Set siblingsToExclude) { + return this.doWithMemberReferredClassProxy(module, proxy -> proxy + .findValueSuggestionsForPrefix(module, fileType, matchesRootTillMe, prefix, + siblingsToExclude), null); } @Override public boolean isLeaf(Module module) { - return doWithMemberReferredClassProxy(module, proxy -> proxy.isLeaf(module), true); + return this.doWithMemberReferredClassProxy(module, proxy -> proxy.isLeaf(module), true); } @Override @@ -144,34 +144,39 @@ public boolean isMetadataNonProperty() { } private boolean computeDeprecationStatus() { - if (member instanceof PsiField) { - return stream(PsiField.class.cast(member).getType().getAnnotations()).anyMatch(annotation -> { + if (this.member instanceof PsiField) { + return stream(PsiField.class.cast(this.member).getType().getAnnotations()).anyMatch(annotation -> { String fqn = annotation.getQualifiedName(); return fqn != null && fqn.equals("java.lang.Deprecated"); }); - } else if (member instanceof PsiMethod) { - return stream(requireNonNull(PsiMethod.class.cast(member).getReturnType()).getAnnotations()) - .anyMatch(annotation -> { - String fqn = annotation.getQualifiedName(); - return fqn != null && fqn.equals("java.lang.Deprecated"); - }); + } else if (this.member instanceof PsiMethod) { + return stream(requireNonNull(PsiMethod.class.cast(this.member).getReturnType()).getAnnotations()) + .anyMatch(annotation -> { + String fqn = annotation.getQualifiedName(); + return fqn != null && fqn.equals("java.lang.Deprecated"); + }); } throw new RuntimeException("Method supports targets of type PsiField & PsiMethod only"); } @NotNull @Override - public Suggestion buildSuggestionForKey(Module module, FileType fileType, - List matchesRootTillMe, int numOfAncestors) { - Icon icon = doWithMemberReferredClassProxy(module, proxy -> proxy.getSuggestionNodeType(module), - SuggestionNodeType.UNKNOWN_CLASS).getIcon(); - Suggestion.SuggestionBuilder builder = - Suggestion.builder().suggestionToDisplay(originalName).description(documentation) - .shortType(shortType).numOfAncestors(numOfAncestors).matchesTopFirst(matchesRootTillMe) - .icon(icon); - if (deprecated) { + public Suggestion buildSuggestionForKey(Module module, FileType fileType, List matchesRootTillMe, int numOfAncestors) { + final Icon icon = this.doWithMemberReferredClassProxy(module, + proxy -> proxy.getSuggestionNodeType(module), SuggestionNodeType.UNKNOWN_CLASS).getIcon(); + + Suggestion.SuggestionBuilder builder = Suggestion.builder() + .suggestionToDisplay(this.originalName) + .description(this.documentation) + .shortType(this.shortType) + .numOfAncestors(numOfAncestors) + .matchesTopFirst(matchesRootTillMe) + .icon(icon); + + if (this.deprecated) { builder.deprecationLevel(SpringConfigurationMetadataDeprecationLevel.warning); } + return builder.fileType(fileType).build(); } @@ -184,21 +189,21 @@ public boolean supportsDocumentation() { @Override public String getDocumentationForKey(Module module, String nodeNavigationPathDotDelimited) { return "" + nodeNavigationPathDotDelimited + "" + new JavaDocumentationProvider() - .generateDoc(member, member); + .generateDoc(this.member, this.member); } @Override public String getDocumentationForValue(Module module, String nodeNavigationPathDotDelimited, - String originalValue) { + String originalValue) { return "" + nodeNavigationPathDotDelimited + " = " + unescapeValue(originalValue) + "" - + new JavaDocumentationProvider().generateDoc(member, member); + + new JavaDocumentationProvider().generateDoc(this.member, this.member); } @NotNull @Override public SuggestionNodeType getSuggestionNodeType(Module module) { - return doWithMemberReferredClassProxy(module, proxy -> proxy.getSuggestionNodeType(module), - SuggestionNodeType.UNKNOWN_CLASS); + return this.doWithMemberReferredClassProxy(module, proxy -> proxy.getSuggestionNodeType(module), + SuggestionNodeType.UNKNOWN_CLASS); } private interface ProxyInvoker { diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/completion/YamlCompletionProvider.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/completion/YamlCompletionProvider.java index 38d0903..aa85562 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/completion/YamlCompletionProvider.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/completion/YamlCompletionProvider.java @@ -31,7 +31,8 @@ import static java.util.Objects.requireNonNull; class YamlCompletionProvider extends CompletionProvider { - @Override + + @Override// TODO -> Refactor this method to reduce its Cognitive Complexity from 34 to the 15 allowed. [+18 locations protected void addCompletions(@NotNull final CompletionParameters completionParameters, final ProcessingContext processingContext, @NotNull final CompletionResultSet resultSet) { @@ -58,27 +59,30 @@ protected void addCompletions(@NotNull final CompletionParameters completionPara return; } if (parent instanceof YAMLSequenceItem) { + for (PsiElement child : parent.getParent().getChildren()) { if (child != parent) { + if (child instanceof YAMLSequenceItem) { - YAMLValue value = YAMLSequenceItem.class.cast(child).getValue(); + final YAMLValue value = ((YAMLSequenceItem) child).getValue(); if (value != null) { siblingsToExclude = getNewIfNotPresent(siblingsToExclude); siblingsToExclude.add(sanitise(value.getText())); } + } else if (child instanceof YAMLKeyValue) { siblingsToExclude = getNewIfNotPresent(siblingsToExclude); - siblingsToExclude.add(sanitise(YAMLKeyValue.class.cast(child).getKeyText())); + siblingsToExclude.add(sanitise(((YAMLKeyValue) child).getKeyText())); } } } + } else if (parent instanceof YAMLMapping) { - for (PsiElement child : parent.getChildren()) { - if (child != elementContext) { - if (child instanceof YAMLKeyValue) { + + for (final PsiElement child : parent.getChildren()) { + if (child != elementContext && child instanceof YAMLKeyValue) { siblingsToExclude = getNewIfNotPresent(siblingsToExclude); - siblingsToExclude.add(sanitise(YAMLKeyValue.class.cast(child).getKeyText())); - } + siblingsToExclude.add(sanitise(((YAMLKeyValue) child).getKeyText())); } } } diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/handler/YamlKeyInsertHandler.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/handler/YamlKeyInsertHandler.java index 8a275fc..a548d4b 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/handler/YamlKeyInsertHandler.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/handler/YamlKeyInsertHandler.java @@ -10,12 +10,14 @@ import in.oneton.idea.spring.assistant.plugin.suggestion.OriginalNameProvider; import in.oneton.idea.spring.assistant.plugin.suggestion.Suggestion; import in.oneton.idea.spring.assistant.plugin.suggestion.SuggestionNodeType; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.yaml.YAMLElementGenerator; import org.jetbrains.yaml.YAMLTokenTypes; import org.jetbrains.yaml.psi.YAMLKeyValue; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import static com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction; import static com.intellij.openapi.editor.EditorModificationUtil.insertStringAtCaret; @@ -29,135 +31,146 @@ public class YamlKeyInsertHandler implements InsertHandler { - @Override - public void handleInsert(final InsertionContext context, final LookupElement lookupElement) { - if (!nextCharAfterSpacesAndQuotesIsColon(getStringAfterAutoCompletedValue(context))) { - String existingIndentation = getExistingIndentation(context, lookupElement); - Suggestion suggestion = (Suggestion) lookupElement.getObject(); - String indentPerLevel = getCodeStyleIntent(context); - Module module = findModule(context); - String suggestionWithCaret = - getSuggestionReplacementWithCaret(module, suggestion, existingIndentation, - indentPerLevel); - String suggestionWithoutCaret = suggestionWithCaret.replace(CARET, ""); - - PsiElement currentElement = context.getFile().findElementAt(context.getStartOffset()); - assert currentElement != null : "no element at " + context.getStartOffset(); - - this.deleteLookupTextAndRetrieveOldValue(context, currentElement); - - insertStringAtCaret(context.getEditor(), suggestionWithoutCaret, false, true, - getCaretIndex(suggestionWithCaret)); + @Override + public void handleInsert(final InsertionContext context, final LookupElement lookupElement) { + if (!nextCharAfterSpacesAndQuotesIsColon(getStringAfterAutoCompletedValue(context))) { + String existingIndentation = getExistingIndentation(context, lookupElement); + Suggestion suggestion = (Suggestion) lookupElement.getObject(); + String indentPerLevel = getCodeStyleIntent(context); + Module module = findModule(context); + String suggestionWithCaret = + getSuggestionReplacementWithCaret(module, suggestion, existingIndentation, + indentPerLevel); + String suggestionWithoutCaret = suggestionWithCaret.replace(CARET, ""); + + PsiElement currentElement = context.getFile().findElementAt(context.getStartOffset()); + assert currentElement != null : "no element at " + context.getStartOffset(); + + this.deleteLookupTextAndRetrieveOldValue(context, currentElement); + + insertStringAtCaret(context.getEditor(), suggestionWithoutCaret, false, true, + getCaretIndex(suggestionWithCaret)); + } } - } - - private int getCaretIndex(final String suggestionWithCaret) { - return suggestionWithCaret.indexOf(CARET); - } - - private String getExistingIndentation(final InsertionContext context, final LookupElement item) { - final String stringBeforeAutoCompletedValue = getStringBeforeAutoCompletedValue(context, item); - return getExistingIndentationInRowStartingFromEnd(stringBeforeAutoCompletedValue); - } - - @NotNull - private String getStringAfterAutoCompletedValue(final InsertionContext context) { - return context.getDocument().getText().substring(context.getTailOffset()); - } - - @NotNull - private String getStringBeforeAutoCompletedValue(final InsertionContext context, - final LookupElement item) { - return context.getDocument().getText() - .substring(0, context.getTailOffset() - item.getLookupString().length()); - } - - private boolean nextCharAfterSpacesAndQuotesIsColon(final String string) { - for (int i = 0; i < string.length(); i++) { - final char c = string.charAt(i); - if (c != ' ' && c != '"') { - return c == ':'; - } + + private int getCaretIndex(final String suggestionWithCaret) { + return suggestionWithCaret.indexOf(CARET); + } + + private String getExistingIndentation(final InsertionContext context, final LookupElement item) { + final String stringBeforeAutoCompletedValue = getStringBeforeAutoCompletedValue(context, item); + return getExistingIndentationInRowStartingFromEnd(stringBeforeAutoCompletedValue); } - return false; - } - - private String getExistingIndentationInRowStartingFromEnd(final String val) { - int count = 0; - for (int i = val.length() - 1; i >= 0; i--) { - final char c = val.charAt(i); - if (c != '\t' && c != ' ' && c != '-') { - break; - } - count++; + + @NotNull + private String getStringAfterAutoCompletedValue(final InsertionContext context) { + return context.getDocument().getText().substring(context.getTailOffset()); } - return val.substring(val.length() - count, val.length()).replaceAll("-", " "); - } - - private void deleteLookupTextAndRetrieveOldValue(InsertionContext context, - @NotNull PsiElement elementAtCaret) { - if (elementAtCaret.getNode().getElementType() != YAMLTokenTypes.SCALAR_KEY) { - deleteLookupPlain(context); - } else { - YAMLKeyValue keyValue = PsiTreeUtil.getParentOfType(elementAtCaret, YAMLKeyValue.class); - assert keyValue != null; - context.commitDocument(); - - // TODO: Whats going on here? - if (keyValue.getValue() != null) { - YAMLKeyValue dummyKV = - YAMLElementGenerator.getInstance(context.getProject()).createYamlKeyValue("foo", "b"); - dummyKV.setValue(keyValue.getValue()); - } - - context.setTailOffset(keyValue.getTextRange().getEndOffset()); - runWriteCommandAction(context.getProject(), - () -> keyValue.getParentMapping().deleteKeyValue(keyValue)); + + @NotNull + private String getStringBeforeAutoCompletedValue(final InsertionContext context, + final LookupElement item) { + return context.getDocument().getText() + .substring(0, context.getTailOffset() - item.getLookupString().length()); + } + + private boolean nextCharAfterSpacesAndQuotesIsColon(final String string) { + for (int i = 0; i < string.length(); i++) { + final char c = string.charAt(i); + if (c != ' ' && c != '"') { + return c == ':'; + } + } + return false; + } + + private String getExistingIndentationInRowStartingFromEnd(final String val) { + int count = 0; + for (int i = val.length() - 1; i >= 0; i--) { + final char c = val.charAt(i); + if (c != '\t' && c != ' ' && c != '-') { + break; + } + count++; + } + return val.substring(val.length() - count, val.length()).replaceAll("-", " "); + } + + private void deleteLookupTextAndRetrieveOldValue(InsertionContext context, + @NotNull PsiElement elementAtCaret) { + if (elementAtCaret.getNode().getElementType() != YAMLTokenTypes.SCALAR_KEY) { + deleteLookupPlain(context); + } else { + YAMLKeyValue keyValue = PsiTreeUtil.getParentOfType(elementAtCaret, YAMLKeyValue.class); + assert keyValue != null; + context.commitDocument(); + + // TODO: Whats going on here? + if (keyValue.getValue() != null) { + YAMLKeyValue dummyKV = + YAMLElementGenerator.getInstance(context.getProject()).createYamlKeyValue("foo", "b"); + dummyKV.setValue(keyValue.getValue()); + } + + context.setTailOffset(keyValue.getTextRange().getEndOffset()); + runWriteCommandAction(context.getProject(), + () -> keyValue.getParentMapping().deleteKeyValue(keyValue)); + } + } + + private void deleteLookupPlain(InsertionContext context) { + Document document = context.getDocument(); + document.deleteString(context.getStartOffset(), context.getTailOffset()); + context.commitDocument(); } - } - - private void deleteLookupPlain(InsertionContext context) { - Document document = context.getDocument(); - document.deleteString(context.getStartOffset(), context.getTailOffset()); - context.commitDocument(); - } - - @NotNull - private String getSuggestionReplacementWithCaret(Module module, Suggestion suggestion, - String existingIndentation, String indentPerLevel) { - StringBuilder builder = new StringBuilder(); - int i = 0; - List matchesTopFirst = suggestion.getMatchesForReplacement(); - do { - OriginalNameProvider nameProvider = matchesTopFirst.get(i); - builder.append("\n").append(existingIndentation).append(getIndent(indentPerLevel, i)) - .append(nameProvider.getOriginalName()).append(":"); - i++; - } while (i < matchesTopFirst.size()); - builder.delete(0, existingIndentation.length() + 1); - String indentForNextLevel = - getOverallIndent(existingIndentation, indentPerLevel, matchesTopFirst.size()); - String sufix = getPlaceholderSufixWithCaret(module, suggestion, indentForNextLevel); - builder.append(sufix); - return builder.toString(); - } - - @NotNull - private String getPlaceholderSufixWithCaret(Module module, Suggestion suggestion, - String indentForNextLevel) { - if (suggestion.getLastSuggestionNode().isMetadataNonProperty()) { - return "\n" + indentForNextLevel + CARET; + + @NotNull + private String getSuggestionReplacementWithCaret(final Module module, final Suggestion suggestion, + final String existingIndentation, final String indentPerLevel) { + + final List matchesTopFirst = suggestion.getMatchesForReplacement(); +// int i = 0; do { OriginalNameProvider nameProvider = matchesTopFirst.get(i); //code i++; } while (i < matchesTopFirst.size()); + + final StringBuilder builder = new StringBuilder(); + final AtomicInteger count = new AtomicInteger(0); + matchesTopFirst.forEach(nameProvider -> + builder.append(StringUtils.LF) + .append(existingIndentation) + .append(getIndent(indentPerLevel, count.getAndIncrement())) + .append(nameProvider.getOriginalName()) + .append(":") + ); + + builder.delete(0, existingIndentation.length() + 1); + + final String indentForNextLevel = getOverallIndent(existingIndentation, indentPerLevel, matchesTopFirst.size()); + final String sufix = getPlaceholderSufixWithCaret(module, suggestion, indentForNextLevel); + + builder.append(sufix); + + return builder.toString(); } - SuggestionNodeType nodeType = suggestion.getSuggestionNodeType(module); - if (nodeType == UNDEFINED || nodeType == UNKNOWN_CLASS) { - return CARET; - } else if (nodeType.representsLeaf()) { - return " " + CARET; - } else if (nodeType.representsArrayOrCollection()) { - return "\n" + indentForNextLevel + "- " + CARET; - } else { // map or class - return "\n" + indentForNextLevel + CARET; + + @NotNull + private String getPlaceholderSufixWithCaret(final Module module, final Suggestion suggestion, final String indentForNextLevel) { + + if (suggestion.getLastSuggestionNode().isMetadataNonProperty()) { + return StringUtils.LF + indentForNextLevel + CARET; + } + + final SuggestionNodeType nodeType = suggestion.getSuggestionNodeType(module); + if (nodeType == UNDEFINED || nodeType == UNKNOWN_CLASS) { + return CARET; + + } else if (nodeType.representsLeaf()) { + return StringUtils.SPACE + CARET; + + } else if (nodeType.representsArrayOrCollection()) { + return StringUtils.LF + indentForNextLevel + "- " + CARET; + + } else { // map or class + return StringUtils.LF + indentForNextLevel + CARET; + } } - } } diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/metadata/MetadataNonPropertySuggestionNode.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/metadata/MetadataNonPropertySuggestionNode.java index 32ded9e..072dc86 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/metadata/MetadataNonPropertySuggestionNode.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/metadata/MetadataNonPropertySuggestionNode.java @@ -109,445 +109,451 @@ @EqualsAndHashCode(of = "name", callSuper = false) public class MetadataNonPropertySuggestionNode extends MetadataSuggestionNode { - private static final Logger log = Logger.getInstance(MetadataNonPropertySuggestionNode.class); - - /** - * Sanitised name used for lookup. `-`, `_` are removed, upper cased characters are converted to lower case - */ - private String name; - /** - * Section of the group/PROPERTY name. Sole purpose of this is to split all properties into their individual part - */ - private String originalName; - - // TODO: Make sure that this will be part of search only if type & sourceType are part of the class path - /** - * Can be null for intermediate nodes that dont have a group entry in `spring-configuration-metadata.json` - */ - @Nullable - private SpringConfigurationMetadataGroup group; - - /** - * Parent reference, for bidirectional navigation. Can be null for roots - */ - @Nullable - private MetadataNonPropertySuggestionNode parent; - /** - * Set of sources these suggestions belong to - */ - private Set belongsTo; - /** - * Child name -> child node. Aids in quick lookup. NOTE: All keys are sanitized - */ - @Nullable - private Map childLookup; - - /** - * Child trie for the nodes at next level, aids in prefix based searching. NOTE: All keys are sanitized - */ - @Nullable - private Trie childrenTrie; - - /** - * @param originalName name that is not sanitised - * @param parent parent MetadataNonPropertySuggestionNode node - * @param belongsTo file/jar containing this property - * @return newly constructed group node - */ - public static MetadataNonPropertySuggestionNode newInstance(String originalName, - @Nullable MetadataNonPropertySuggestionNode parent, String belongsTo) { - MetadataNonPropertySuggestionNodeBuilder builder = - MetadataNonPropertySuggestionNode.builder().name(SuggestionNode.sanitise(originalName)) - .originalName(originalName).parent(parent); - Set belongsToSet = new THashSet<>(); - belongsToSet.add(belongsTo); - builder.belongsTo(belongsToSet); - return builder.build(); - } - - @Override - public MetadataSuggestionNode findDeepestMetadataNode(String[] pathSegments, - int pathSegmentStartIndex, boolean matchAllSegments) { - MetadataSuggestionNode deepestMatch = null; - if (!matchAllSegments) { - deepestMatch = this; + private static final Logger log = Logger.getInstance(MetadataNonPropertySuggestionNode.class); + + /** + * Sanitised name used for lookup. `-`, `_` are removed, upper cased characters are converted to lower case + */ + private String name; + /** + * Section of the group/PROPERTY name. Sole purpose of this is to split all properties into their individual part + */ + private String originalName; + + // TODO: Make sure that this will be part of search only if type & sourceType are part of the class path + /** + * Can be null for intermediate nodes that dont have a group entry in `spring-configuration-metadata.json` + */ + @Nullable + private SpringConfigurationMetadataGroup group; + + /** + * Parent reference, for bidirectional navigation. Can be null for roots + */ + @Nullable + private MetadataNonPropertySuggestionNode parent; + /** + * Set of sources these suggestions belong to + */ + private Set belongsTo; + /** + * Child name -> child node. Aids in quick lookup. NOTE: All keys are sanitized + */ + @Nullable + private Map childLookup; + + /** + * Child trie for the nodes at next level, aids in prefix based searching. NOTE: All keys are sanitized + */ + @Nullable + private Trie childrenTrie; + + /** + * @param originalName name that is not sanitised + * @param parent parent MetadataNonPropertySuggestionNode node + * @param belongsTo file/jar containing this property + * @return newly constructed group node + */ + public static MetadataNonPropertySuggestionNode newInstance(String originalName, + @Nullable MetadataNonPropertySuggestionNode parent, String belongsTo) { + MetadataNonPropertySuggestionNodeBuilder builder = + MetadataNonPropertySuggestionNode.builder().name(SuggestionNode.sanitise(originalName)) + .originalName(originalName).parent(parent); + Set belongsToSet = new THashSet<>(); + belongsToSet.add(belongsTo); + builder.belongsTo(belongsToSet); + return builder.build(); } - boolean haveMoreSegments = pathSegmentStartIndex < pathSegments.length; - if (haveMoreSegments) { - boolean lastSegment = pathSegmentStartIndex == (pathSegments.length - 1); - String pathSegment = pathSegments[pathSegmentStartIndex]; - if (hasChildren()) { - assert childLookup != null; - if (childLookup.containsKey(pathSegment)) { - MetadataSuggestionNode child = childLookup.get(pathSegment); - if (lastSegment) { - deepestMatch = child; - } else { - deepestMatch = child - .findDeepestMetadataNode(pathSegments, pathSegmentStartIndex + 1, matchAllSegments); - } - if (matchAllSegments && deepestMatch == null) { + + @Override + public MetadataSuggestionNode findDeepestMetadataNode(String[] pathSegments, int pathSegmentStartIndex, boolean matchAllSegments) { + + MetadataSuggestionNode deepestMatch = null; + if (!matchAllSegments) { + deepestMatch = this; + } + + boolean haveMoreSegments = pathSegmentStartIndex < pathSegments.length; + if (!haveMoreSegments) { + return deepestMatch; + } + + boolean lastSegment = pathSegmentStartIndex == (pathSegments.length - 1); + String pathSegment = pathSegments[pathSegmentStartIndex]; + + if (hasChildren()) { + assert childLookup != null; + if (childLookup.containsKey(pathSegment)) { + MetadataSuggestionNode child = childLookup.get(pathSegment); + if (lastSegment) { + deepestMatch = child; + } else { + deepestMatch = child.findDeepestMetadataNode(pathSegments, pathSegmentStartIndex + 1, matchAllSegments); + } + + if (matchAllSegments && deepestMatch == null) { + deepestMatch = this; + } + } + + } else if (lastSegment && name.equals(pathSegment)) { deepestMatch = this; - } } - } else if (lastSegment && name.equals(pathSegment)) { - deepestMatch = this; - } + return deepestMatch; } - return deepestMatch; - } - - @Nullable - @Override - public List findDeepestSuggestionNode(Module module, - List matchesRootTillMe, String[] pathSegments, int pathSegmentStartIndex) { - List deepestMatch = null; - boolean haveMoreSegments = pathSegmentStartIndex < pathSegments.length; - if (haveMoreSegments) { - String currentPathSegment = pathSegments[pathSegmentStartIndex]; - boolean lastSegment = pathSegmentStartIndex == (pathSegments.length - 1); - if (hasChildren()) { - assert childLookup != null; - if (childLookup.containsKey(currentPathSegment)) { - MetadataSuggestionNode child = childLookup.get(currentPathSegment); - matchesRootTillMe.add(child); - if (lastSegment) { + + @Nullable + @Override + public List findDeepestSuggestionNode(Module module, + List matchesRootTillMe, String[] pathSegments, int pathSegmentStartIndex) { + List deepestMatch = null; + boolean haveMoreSegments = pathSegmentStartIndex < pathSegments.length; + if (haveMoreSegments) { + String currentPathSegment = pathSegments[pathSegmentStartIndex]; + boolean lastSegment = pathSegmentStartIndex == (pathSegments.length - 1); + if (hasChildren()) { + assert childLookup != null; + if (childLookup.containsKey(currentPathSegment)) { + MetadataSuggestionNode child = childLookup.get(currentPathSegment); + matchesRootTillMe.add(child); + if (lastSegment) { + deepestMatch = matchesRootTillMe; + } else { + deepestMatch = child.findDeepestSuggestionNode(module, matchesRootTillMe, pathSegments, + pathSegmentStartIndex + 1); + } + } + } else if (lastSegment && name.equals(currentPathSegment)) { + deepestMatch = matchesRootTillMe; + } + } else { deepestMatch = matchesRootTillMe; - } else { - deepestMatch = child.findDeepestSuggestionNode(module, matchesRootTillMe, pathSegments, - pathSegmentStartIndex + 1); - } } - } else if (lastSegment && name.equals(currentPathSegment)) { - deepestMatch = matchesRootTillMe; - } - } else { - deepestMatch = matchesRootTillMe; + + return deepestMatch; } - return deepestMatch; - } - - public void addChildren(Module module, SpringConfigurationMetadataGroup group, - String[] rawPathSegments, int startIndex, String belongsTo) { - MetadataNonPropertySuggestionNode groupNode = - addChildren(rawPathSegments, startIndex, rawPathSegments.length - 1, belongsTo); - groupNode.setGroup(module, group); - } - - public void addChildren(SpringConfigurationMetadataProperty property, String[] rawPathSegments, - int startIndex, String belongsTo) { - MetadataNonPropertySuggestionNode parentNode; - // since last property is the actual property, lets only add children only till last but one - int endIndexIncl = rawPathSegments.length - 2; - if (startIndex <= endIndexIncl) { - parentNode = addChildren(rawPathSegments, startIndex, endIndexIncl, belongsTo); - } else { - parentNode = this; - addRefCascadeTillRoot(belongsTo); + public void addChildren(Module module, SpringConfigurationMetadataGroup group, + String[] rawPathSegments, int startIndex, String belongsTo) { + MetadataNonPropertySuggestionNode groupNode = + addChildren(rawPathSegments, startIndex, rawPathSegments.length - 1, belongsTo); + groupNode.setGroup(module, group); } - parentNode.addProperty(property, rawPathSegments[rawPathSegments.length - 1], belongsTo); - } - - @Override - @Nullable - public SortedSet findKeySuggestionsForQueryPrefix(Module module, FileType fileType, - List matchesRootTillMe, int numOfAncestors, String[] querySegmentPrefixes, - int querySegmentPrefixStartIndex, @Nullable Set siblingsToExclude) { - boolean lookingForConcreteNode = querySegmentPrefixStartIndex >= querySegmentPrefixes.length; - if (lookingForConcreteNode) { - if (isGroup()) { - // If we have only one child, lets send the child value directly instead of this node. This way user does not need trigger suggestion for level, esp. when we know there will is only be one child - if (hasOnlyOneChild(module)) { - assert childrenTrie != null; - return addChildToMatchesAndSearchInNextLevel(module, fileType, matchesRootTillMe, - numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex, - childrenTrie.values()); - } else { // either there are no children/multiple children are present. Lets return suggestions - assert group != null; - return newSingleElementSortedSet( - group.newSuggestion(fileType, matchesRootTillMe, numOfAncestors)); + public void addChildren(SpringConfigurationMetadataProperty property, String[] rawPathSegments, + int startIndex, String belongsTo) { + MetadataNonPropertySuggestionNode parentNode; + // since last property is the actual property, lets only add children only till last but one + int endIndexIncl = rawPathSegments.length - 2; + if (startIndex <= endIndexIncl) { + parentNode = addChildren(rawPathSegments, startIndex, endIndexIncl, belongsTo); + } else { + parentNode = this; + addRefCascadeTillRoot(belongsTo); } - } else { // intermediate node, lets get all next level groups & properties - assert childrenTrie != null; - return addChildToMatchesAndSearchInNextLevel(module, fileType, matchesRootTillMe, - numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex, - childrenTrie.values()); - } - } else { - if (hasChildren()) { + + parentNode.addProperty(property, rawPathSegments[rawPathSegments.length - 1], belongsTo); + } + + @Override + @Nullable + public SortedSet findKeySuggestionsForQueryPrefix(final Module module, FileType fileType, + final List matchesRootTillMe, + final int numOfAncestors, String[] querySegmentPrefixes, + int querySegmentPrefixStartIndex, + @Nullable final Set siblingsToExclude) { + + boolean lookingForConcreteNode = querySegmentPrefixStartIndex >= querySegmentPrefixes.length; + if (lookingForConcreteNode) { + return lookingForConcreteNode(module, fileType, matchesRootTillMe, numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex); + + } else if (hasChildren()) { + return getSuggestionsChildren(module, fileType, matchesRootTillMe, numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex, siblingsToExclude); + } + return null; + } + + private SortedSet getSuggestionsChildren(final Module module, final FileType fileType, final List matchesRootTillMe, + final int numOfAncestors, final String[] querySegmentPrefixes, final int querySegmentPrefixStartIndex, + final @org.jetbrains.annotations.Nullable Set siblingsToExclude) { assert childrenTrie != null; assert childLookup != null; String querySegmentPrefix = querySegmentPrefixes[querySegmentPrefixStartIndex]; SortedMap sortedPrefixToMetadataNode = - childrenTrie.prefixMap(querySegmentPrefix); + childrenTrie.prefixMap(querySegmentPrefix); Collection matchedChildren = sortedPrefixToMetadataNode.values(); Set exclusionMembers = null; if (siblingsToExclude != null) { - exclusionMembers = siblingsToExclude.stream().map(childLookup::get).collect(toSet()); + exclusionMembers = siblingsToExclude.stream().map(childLookup::get).collect(toSet()); } if (!isEmpty(exclusionMembers) && !isEmpty(matchedChildren)) { - Set finalExclusionMembers = exclusionMembers; - matchedChildren = - matchedChildren.stream().filter(value -> !finalExclusionMembers.contains(value)) - .collect(toList()); + final Set finalExclusionMembers = exclusionMembers; + matchedChildren = matchedChildren.stream() + .filter(value -> !finalExclusionMembers.contains(value)) + .collect(toList()); } - if (matchedChildren.size() != 0) { - SortedSet suggestions = - addChildToMatchesAndSearchInNextLevel(module, fileType, matchesRootTillMe, - numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex + 1, - matchedChildren); - // If the leaf is deprecated (management.context-path), it will shadow deeper match (management.server.servlet.context-path) - if (suggestions != null) { - return suggestions; - } else { - // lets search in the next level - return addChildToMatchesAndSearchInNextLevel(module, fileType, matchesRootTillMe, - numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex, - computeChildrenToIterateOver(childLookup, exclusionMembers)); - } + int segmentPrefixStartIndex = querySegmentPrefixStartIndex; + if (matchedChildren.size() == 0) { + matchedChildren = computeChildrenToIterateOver(childLookup, exclusionMembers); } else { - // lets search in the next level - return addChildToMatchesAndSearchInNextLevel(module, fileType, matchesRootTillMe, - numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex, - computeChildrenToIterateOver(childLookup, exclusionMembers)); + segmentPrefixStartIndex = segmentPrefixStartIndex + 1; } - } - return null; + // lets search in the next level + return addChildToMatchesAndSearchInNextLevel(module, fileType, matchesRootTillMe, numOfAncestors, + querySegmentPrefixes, segmentPrefixStartIndex, matchedChildren); } - } - - @Override - protected boolean hasOnlyOneChild(Module module) { - return childrenTrie != null && childrenTrie.size() == 1; - // && childrenTrie.values().stream() - // .allMatch(MetadataSuggestionNode::hasOnlyOneChild) - } - - @Override - public String toTree() { - StringBuilder builder = new StringBuilder(originalName) - .append(isRoot() ? "(root + group)" : (isGroup() ? "(group)" : "(intermediate)")) - .append("\n"); - if (childLookup != null) { - childLookup.forEach( - (k, v) -> builder.append(v.toTree().trim().replaceAll("^", " ").replaceAll("\n", "\n ")) - .append("\n")); + + private SortedSet lookingForConcreteNode(final Module module, final FileType fileType, final List matchesRootTillMe, final int numOfAncestors, final String[] querySegmentPrefixes, final int querySegmentPrefixStartIndex) { + if (isGroup()) { + // If we have only one child, lets send the child value directly instead of this node. This way user does not need trigger suggestion for level, esp. when we know there will is only be one child + if (hasOnlyOneChild(module)) { + assert childrenTrie != null; + return addChildToMatchesAndSearchInNextLevel(module, fileType, matchesRootTillMe, + numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex, + childrenTrie.values()); + } else { // either there are no children/multiple children are present. Lets return suggestions + assert group != null; + return newSingleElementSortedSet( + group.newSuggestion(fileType, matchesRootTillMe, numOfAncestors)); + } + } else { // intermediate node, lets get all next level groups & properties + assert childrenTrie != null; + return addChildToMatchesAndSearchInNextLevel(module, fileType, matchesRootTillMe, + numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex, + childrenTrie.values()); + } } - return builder.toString(); - } - - /** - * @param containerPath Represents path to the metadata file container - * @return true if no children left & this item does not belong to any other source - */ - @Override - public boolean removeRefCascadeDown(String containerPath) { - belongsTo.remove(containerPath); - // If the current node & all its children belong to a single file, lets remove the whole tree - if (belongsTo.size() == 0) { - return true; + + @Override + protected boolean hasOnlyOneChild(Module module) { + return childrenTrie != null && childrenTrie.size() == 1; + // && childrenTrie.values().stream() + // .allMatch(MetadataSuggestionNode::hasOnlyOneChild) } - if (hasChildren()) { - assert childLookup != null; - assert childrenTrie != null; - Iterator iterator = childrenTrie.values().iterator(); - while (iterator.hasNext()) { - MetadataSuggestionNode child = iterator.next(); - boolean canRemoveReference = child.removeRefCascadeDown(containerPath); - if (canRemoveReference) { - iterator.remove(); - childLookup.remove(child.getName()); - childrenTrie.remove(child.getName()); + @Override + public String toTree() { + StringBuilder builder = new StringBuilder(originalName) + .append(isRoot() ? "(root + group)" : (isGroup() ? "(group)" : "(intermediate)")) + .append("\n"); + if (childLookup != null) { + childLookup.forEach( + (k, v) -> builder.append(v.toTree().trim().replaceAll("^", " ").replaceAll("\n", "\n ")) + .append("\n")); } - } - if (!hasChildren()) { - childLookup = null; - childrenTrie = null; - } + return builder.toString(); } - return false; - } - - @Override - protected boolean isRoot() { - return parent == null; - } - - @Override - public boolean isGroup() { - return group != null; - } - - @Override - public boolean isProperty() { - return false; - } - - @NotNull - @Override - public String getDocumentationForKey(Module module, String nodeNavigationPathDotDelimited) { - if (isGroup()) { - assert group != null; - return group.getDocumentation(nodeNavigationPathDotDelimited); + + /** + * @param containerPath Represents path to the metadata file container + * @return true if no children left & this item does not belong to any other source + */ + @Override + public boolean removeRefCascadeDown(String containerPath) { + belongsTo.remove(containerPath); + // If the current node & all its children belong to a single file, lets remove the whole tree + if (belongsTo.size() == 0) { + return true; + } + + if (hasChildren()) { + assert childLookup != null; + assert childrenTrie != null; + Iterator iterator = childrenTrie.values().iterator(); + while (iterator.hasNext()) { + MetadataSuggestionNode child = iterator.next(); + boolean canRemoveReference = child.removeRefCascadeDown(containerPath); + if (canRemoveReference) { + iterator.remove(); + childLookup.remove(child.getName()); + childrenTrie.remove(child.getName()); + } + } + if (!hasChildren()) { + childLookup = null; + childrenTrie = null; + } + } + return false; } - throw new RuntimeException( - "Documentation not supported for this element. Call supportsDocumentation() first"); - } - - @Nullable - @Override - public SortedSet findValueSuggestionsForPrefix(Module module, FileType fileType, - List matchesRootTillMe, String prefix, - @Nullable Set siblingsToExclude) { - throw new IllegalAccessError("Should never be called"); - } - - @Nullable - @Override - public String getDocumentationForValue(Module module, String nodeNavigationPathDotDelimited, - String originalValue) { - throw new IllegalAccessError("Should never be called"); - } - - @Override - public boolean isLeaf(Module module) { - return false; - } - - @Override - public boolean isMetadataNonProperty() { - return true; - } - - private boolean hasChildren() { - return childrenTrie != null && childrenTrie.size() != 0; - } - - @NotNull - @Override - public SuggestionNodeType getSuggestionNodeType(Module module) { - if (isGroup()) { - assert group != null; - return group.getNodeType(); - } else { - return SuggestionNodeType.UNDEFINED; + + @Override + protected boolean isRoot() { + return parent == null; } - } - - public void setGroup(Module module, SpringConfigurationMetadataGroup group) { - updateGroupType(module, group); - this.group = group; - } - - @Override - public void refreshClassProxy(Module module) { - updateGroupType(module, group); - if (hasChildren()) { - assert childLookup != null; - childLookup.values().forEach(child -> child.refreshClassProxy(module)); + + @Override + public boolean isGroup() { + return group != null; } - } - - private Collection computeChildrenToIterateOver( - @NotNull Map childLookup, - Set exclusionMembers) { - Collection childrenToIterateOver; - if (!isEmpty(exclusionMembers)) { - childrenToIterateOver = - childLookup.values().stream().filter(value -> !exclusionMembers.contains(value)) - .collect(toList()); - } else { - childrenToIterateOver = childLookup.values(); + + @Override + public boolean isProperty() { + return false; } - return childrenToIterateOver; - } - - private void addProperty(SpringConfigurationMetadataProperty property, String originalName, - String belongsTo) { - addRefCascadeTillRoot(belongsTo); - if (!hasChildren()) { - childLookup = new THashMap<>(); - childrenTrie = new PatriciaTrie<>(); + + @NotNull + @Override + public String getDocumentationForKey(Module module, String nodeNavigationPathDotDelimited) { + if (isGroup()) { + assert group != null; + return group.getDocumentation(nodeNavigationPathDotDelimited); + } + throw new RuntimeException( + "Documentation not supported for this element. Call supportsDocumentation() first"); } - assert childLookup != null; - assert childrenTrie != null; - MetadataSuggestionNode childNode = - MetadataPropertySuggestionNode.newInstance(originalName, property, this, belongsTo); - - String name = SuggestionNode.sanitise(originalName); - childLookup.put(name, childNode); - childrenTrie.put(name, childNode); - } - - private MetadataNonPropertySuggestionNode addChildren(String[] rawPathSegments, int startIndex, - int endIndexIncl, String belongsTo) { - addRefCascadeTillRoot(belongsTo); - if (!hasChildren()) { - childLookup = new THashMap<>(); - childrenTrie = new PatriciaTrie<>(); + @Nullable + @Override + public SortedSet findValueSuggestionsForPrefix(Module module, FileType fileType, + List matchesRootTillMe, String prefix, + @Nullable Set siblingsToExclude) { + throw new IllegalAccessError("Should never be called"); } - assert childLookup != null; - assert childrenTrie != null; + @Nullable + @Override + public String getDocumentationForValue(Module module, String nodeNavigationPathDotDelimited, + String originalValue) { + throw new IllegalAccessError("Should never be called"); + } - String rawPathSegment = rawPathSegments[startIndex]; - String pathSegment = SuggestionNode.sanitise(rawPathSegment); - MetadataNonPropertySuggestionNode childNode = - MetadataNonPropertySuggestionNode.class.cast(childLookup.get(pathSegment)); - if (childNode == null) { - childNode = MetadataNonPropertySuggestionNode.newInstance(rawPathSegment, this, belongsTo); - childNode.setParent(this); + @Override + public boolean isLeaf(Module module) { + return false; + } + + @Override + public boolean isMetadataNonProperty() { + return true; + } + + private boolean hasChildren() { + return childrenTrie != null && childrenTrie.size() != 0; + } + + @NotNull + @Override + public SuggestionNodeType getSuggestionNodeType(Module module) { + if (isGroup()) { + assert group != null; + return group.getNodeType(); + } else { + return SuggestionNodeType.UNDEFINED; + } + } + + public void setGroup(Module module, SpringConfigurationMetadataGroup group) { + updateGroupType(module, group); + this.group = group; + } + + @Override + public void refreshClassProxy(Module module) { + updateGroupType(module, group); + if (hasChildren()) { + assert childLookup != null; + childLookup.values().forEach(child -> child.refreshClassProxy(module)); + } + } + + private Collection computeChildrenToIterateOver( + @NotNull Map childLookup, + Set exclusionMembers) { + Collection childrenToIterateOver; + if (!isEmpty(exclusionMembers)) { + childrenToIterateOver = + childLookup.values().stream().filter(value -> !exclusionMembers.contains(value)) + .collect(toList()); + } else { + childrenToIterateOver = childLookup.values(); + } + return childrenToIterateOver; + } - childLookup.put(pathSegment, childNode); - childrenTrie.put(pathSegment, childNode); + private void addProperty(SpringConfigurationMetadataProperty property, String originalName, + String belongsTo) { + addRefCascadeTillRoot(belongsTo); + if (!hasChildren()) { + childLookup = new THashMap<>(); + childrenTrie = new PatriciaTrie<>(); + } + + assert childLookup != null; + assert childrenTrie != null; + MetadataSuggestionNode childNode = + MetadataPropertySuggestionNode.newInstance(originalName, property, this, belongsTo); + + String name = SuggestionNode.sanitise(originalName); + childLookup.put(name, childNode); + childrenTrie.put(name, childNode); } - // If this is the last segment, lets set group - if (startIndex >= endIndexIncl) { - return childNode; - } else { - return childNode.addChildren(rawPathSegments, startIndex + 1, endIndexIncl, belongsTo); + private MetadataNonPropertySuggestionNode addChildren(String[] rawPathSegments, int startIndex, + int endIndexIncl, String belongsTo) { + addRefCascadeTillRoot(belongsTo); + if (!hasChildren()) { + childLookup = new THashMap<>(); + childrenTrie = new PatriciaTrie<>(); + } + + assert childLookup != null; + assert childrenTrie != null; + + String rawPathSegment = rawPathSegments[startIndex]; + String pathSegment = SuggestionNode.sanitise(rawPathSegment); + MetadataNonPropertySuggestionNode childNode = + MetadataNonPropertySuggestionNode.class.cast(childLookup.get(pathSegment)); + if (childNode == null) { + childNode = MetadataNonPropertySuggestionNode.newInstance(rawPathSegment, this, belongsTo); + childNode.setParent(this); + + childLookup.put(pathSegment, childNode); + childrenTrie.put(pathSegment, childNode); + } + + // If this is the last segment, lets set group + if (startIndex >= endIndexIncl) { + return childNode; + } else { + return childNode.addChildren(rawPathSegments, startIndex + 1, endIndexIncl, belongsTo); + } } - } - - private SortedSet addChildToMatchesAndSearchInNextLevel(Module module, - FileType fileType, List matchesRootTillParentNode, int numOfAncestors, - String[] querySegmentPrefixes, int querySegmentPrefixStartIndex, - Collection childNodes) { - SortedSet suggestions = null; - for (MetadataSuggestionNode child : childNodes) { - List matchesRootTillChild = - unmodifiableList(newListWithMembers(matchesRootTillParentNode, child)); - Set matchedSuggestions = child - .findKeySuggestionsForQueryPrefix(module, fileType, matchesRootTillChild, numOfAncestors, - querySegmentPrefixes, querySegmentPrefixStartIndex, null); - if (matchedSuggestions != null) { - if (suggestions == null) { - suggestions = new TreeSet<>(); + + private SortedSet addChildToMatchesAndSearchInNextLevel(Module module, + FileType fileType, List matchesRootTillParentNode, int numOfAncestors, + String[] querySegmentPrefixes, int querySegmentPrefixStartIndex, + Collection childNodes) { + SortedSet suggestions = null; + for (MetadataSuggestionNode child : childNodes) { + List matchesRootTillChild = + unmodifiableList(newListWithMembers(matchesRootTillParentNode, child)); + Set matchedSuggestions = child + .findKeySuggestionsForQueryPrefix(module, fileType, matchesRootTillChild, numOfAncestors, + querySegmentPrefixes, querySegmentPrefixStartIndex, null); + if (matchedSuggestions != null) { + if (suggestions == null) { + suggestions = new TreeSet<>(); + } + suggestions.addAll(matchedSuggestions); + } } - suggestions.addAll(matchedSuggestions); - } + return suggestions; } - return suggestions; - } - - private void updateGroupType(Module module, SpringConfigurationMetadataGroup group) { - if (group != null && group.getClassName() != null) { - PsiType groupPsiType = safeGetValidType(module, group.getClassName()); - if (groupPsiType != null) { - group.setNodeType(PsiCustomUtil.getSuggestionNodeType(groupPsiType)); - } else { - group.setNodeType(SuggestionNodeType.UNKNOWN_CLASS); - } + + private void updateGroupType(Module module, SpringConfigurationMetadataGroup group) { + if (group != null && group.getClassName() != null) { + PsiType groupPsiType = safeGetValidType(module, group.getClassName()); + if (groupPsiType != null) { + group.setNodeType(PsiCustomUtil.getSuggestionNodeType(groupPsiType)); + } else { + group.setNodeType(SuggestionNodeType.UNKNOWN_CLASS); + } + } } - } } diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/metadata/MetadataPropertySuggestionNode.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/metadata/MetadataPropertySuggestionNode.java index 8ad1d81..786f2ad 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/metadata/MetadataPropertySuggestionNode.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/metadata/MetadataPropertySuggestionNode.java @@ -36,197 +36,195 @@ @EqualsAndHashCode(of = "name", callSuper = false) public class MetadataPropertySuggestionNode extends MetadataSuggestionNode { - /** - * Sanitised name used for lookup. `-`, `_` are removed, upper cased characters are converted to lower case - */ - private String name; - /** - * Section of the group/PROPERTY name. Sole purpose of this is to split all properties into their individual part - */ - private String originalName; - /** - * Parent reference, for bidirectional navigation. Can be null for roots - */ - @Nullable - private MetadataNonPropertySuggestionNode parent; - /** - * Set of sources these suggestions belong to - */ - private Set belongsTo; - // TODO: Make sure that this will be part of search only if type & sourceType are part of the class path - private SpringConfigurationMetadataProperty property; - - /** - * @param originalName name that is not sanitised - * @param property property to associate - * @param parent parent MetadataNonPropertySuggestionNode node - * @param belongsTo file/jar containing this property - * @return newly constructed property node - */ - public static MetadataPropertySuggestionNode newInstance(String originalName, - @NotNull SpringConfigurationMetadataProperty property, - @Nullable MetadataNonPropertySuggestionNode parent, String belongsTo) { - MetadataPropertySuggestionNode.MetadataPropertySuggestionNodeBuilder builder = - MetadataPropertySuggestionNode.builder().name(sanitise(originalName)) - .originalName(originalName).property(property).parent(parent); - Set belongsToSet = new THashSet<>(); - belongsToSet.add(belongsTo); - builder.belongsTo(belongsToSet); - return builder.build(); - } - - /** - * A property node can represent either leaf/an object depending on `type` & `hint`s associated with `SpringConfigurationMetadataProperty` - * - * @param module module - * @return true if leaf, false otherwise - */ - @Override - public boolean isLeaf(Module module) { - return property.isLeaf(module); - } - - @Override - public boolean isMetadataNonProperty() { - return false; - } - - /** - * Type information can come from `hint` & `type` attribute of `SpringConfigurationMetadataProperty` - * - * @param module module - * @return node type - */ - @NotNull - @Override - public SuggestionNodeType getSuggestionNodeType(Module module) { - return property.getSuggestionNodeType(module); - } - - @NotNull - @Override - public String getOriginalName() { - return originalName; - } - - @Override - public MetadataSuggestionNode findDeepestMetadataNode(String[] pathSegments, - int pathSegmentStartIndex, boolean matchAllSegments) { - MetadataSuggestionNode deepestMatch = null; - if (!matchAllSegments) { - deepestMatch = this; - } - boolean haveMoreSegments = pathSegmentStartIndex < pathSegments.length; - if (haveMoreSegments) { - String pathSegment = pathSegments[pathSegmentStartIndex]; - boolean lastSegment = pathSegmentStartIndex == (pathSegments.length - 1); - // since this is the last node in the metadata subsection of the tree, lets just return if this is not the last segment & last node - if (name.equals(pathSegment) && lastSegment) { - deepestMatch = this; - } - } - - return deepestMatch; - } - - @Nullable - @Override - public List findDeepestSuggestionNode(Module module, - List matchesRootTillParentNode, String[] pathSegments, - int pathSegmentStartIndex) { - List deepestMatch = null; - boolean haveMoreSegments = pathSegmentStartIndex < pathSegments.length; - if (haveMoreSegments) { - if (!property.isLeaf(module)) { - deepestMatch = property - .findChildDeepestKeyMatch(module, matchesRootTillParentNode, pathSegments, - pathSegmentStartIndex); - } - } - - return deepestMatch; - } - - @Nullable - @Override - public SortedSet findKeySuggestionsForQueryPrefix(Module module, FileType fileType, - List matchesRootTillMe, int numOfAncestors, String[] querySegmentPrefixes, - int querySegmentPrefixStartIndex, @Nullable Set siblingsToExclude) { - if (!property.isDeprecatedError()) { - boolean lookingForConcreteNode = querySegmentPrefixStartIndex >= querySegmentPrefixes.length; - if (lookingForConcreteNode) { - return newSingleElementSortedSet( - property.buildKeySuggestion(module, fileType, matchesRootTillMe, numOfAncestors)); - } else { - if (!property.isLeaf(module)) { - return property.findChildKeySuggestionsForQueryPrefix(module, fileType, matchesRootTillMe, - numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex, - siblingsToExclude); + /** + * Sanitised name used for lookup. `-`, `_` are removed, upper cased characters are converted to lower case + */ + private String name; + /** + * Section of the group/PROPERTY name. Sole purpose of this is to split all properties into their individual part + */ + private String originalName; + /** + * Parent reference, for bidirectional navigation. Can be null for roots + */ + @Nullable + private MetadataNonPropertySuggestionNode parent; + /** + * Set of sources these suggestions belong to + */ + private Set belongsTo; + // TODO: Make sure that this will be part of search only if type & sourceType are part of the class path + private SpringConfigurationMetadataProperty property; + + /** + * @param originalName name that is not sanitised + * @param property property to associate + * @param parent parent MetadataNonPropertySuggestionNode node + * @param belongsTo file/jar containing this property + * @return newly constructed property node + */ + public static MetadataPropertySuggestionNode newInstance(String originalName, + @NotNull SpringConfigurationMetadataProperty property, + @Nullable MetadataNonPropertySuggestionNode parent, String belongsTo) { + MetadataPropertySuggestionNode.MetadataPropertySuggestionNodeBuilder builder = + MetadataPropertySuggestionNode.builder().name(sanitise(originalName)) + .originalName(originalName).property(property).parent(parent); + Set belongsToSet = new THashSet<>(); + belongsToSet.add(belongsTo); + builder.belongsTo(belongsToSet); + return builder.build(); + } + + /** + * A property node can represent either leaf/an object depending on `type` & `hint`s associated with `SpringConfigurationMetadataProperty` + * + * @param module module + * @return true if leaf, false otherwise + */ + @Override + public boolean isLeaf(Module module) { + return property.isLeaf(module); + } + + @Override + public boolean isMetadataNonProperty() { + return false; + } + + /** + * Type information can come from `hint` & `type` attribute of `SpringConfigurationMetadataProperty` + * + * @param module module + * @return node type + */ + @NotNull + @Override + public SuggestionNodeType getSuggestionNodeType(Module module) { + return property.getSuggestionNodeType(module); + } + + @NotNull + @Override + public String getOriginalName() { + return originalName; + } + + @Override + public MetadataSuggestionNode findDeepestMetadataNode(String[] pathSegments, + int pathSegmentStartIndex, boolean matchAllSegments) { + MetadataSuggestionNode deepestMatch = null; + if (!matchAllSegments) { + deepestMatch = this; + } + boolean haveMoreSegments = pathSegmentStartIndex < pathSegments.length; + if (haveMoreSegments) { + String pathSegment = pathSegments[pathSegmentStartIndex]; + boolean lastSegment = pathSegmentStartIndex == (pathSegments.length - 1); + // since this is the last node in the metadata subsection of the tree, lets just return if this is not the last segment & last node + if (name.equals(pathSegment) && lastSegment) { + deepestMatch = this; + } } - } - } - return null; - } - - @Nullable - @Override - public String getDocumentationForKey(Module module, String nodeNavigationPathDotDelimited) { - return property.getDocumentationForKey(nodeNavigationPathDotDelimited); - } - - @Nullable - @Override - public SortedSet findValueSuggestionsForPrefix(Module module, FileType fileType, - List matchesRootTillMe, String prefix, - @Nullable Set siblingsToExclude) { - return property - .findSuggestionsForValues(module, fileType, matchesRootTillMe, prefix, siblingsToExclude); - } - - @Nullable - @Override - public String getDocumentationForValue(Module module, String nodeNavigationPathDotDelimited, - String originalValue) { - return property.getDocumentationForValue(module, nodeNavigationPathDotDelimited, originalValue); - } - - @Override - protected boolean isRoot() { - return parent == null; - } - - @Override - public boolean isGroup() { - return false; - } - - @Override - public boolean isProperty() { - return true; - } - - @Override - protected boolean hasOnlyOneChild(Module module) { - // since we have to delegate any further lookups to the delegate (which has additional cost associated with parsing & building childrenTrie dynamically) - // lets always lie to caller that we have more than one child so that the search terminates at this node on the initial lookup - return false; - } - - @Override - public String toTree() { - return originalName + (isRoot() ? "(root + property)" : "(property)"); - } - - @Override - public boolean removeRefCascadeDown(String containerPath) { - belongsTo.remove(containerPath); - // If the current node & all its children belong to a single file, lets remove the whole tree - return belongsTo.size() == 0; - } - - @Override - public void refreshClassProxy(Module module) { - property.refreshDelegate(module); - } + + return deepestMatch; + } + + @Nullable + @Override + public List findDeepestSuggestionNode(Module module, + List matchesRootTillParentNode, String[] pathSegments, + int pathSegmentStartIndex) { + + boolean haveMoreSegments = pathSegmentStartIndex < pathSegments.length; + if (haveMoreSegments && !property.isLeaf(module)) { + return property.findChildDeepestKeyMatch(module, matchesRootTillParentNode, pathSegments, pathSegmentStartIndex); + } + + return null; + } + + @Nullable + @Override + public SortedSet findKeySuggestionsForQueryPrefix(final Module module, final FileType fileType, + final List matchesRootTillMe, + final int numOfAncestors, + final String[] querySegmentPrefixes, + final int querySegmentPrefixStartIndex, + @Nullable final Set siblingsToExclude) { + if (!property.isDeprecatedError()) { + boolean lookingForConcreteNode = querySegmentPrefixStartIndex >= querySegmentPrefixes.length; + if (lookingForConcreteNode) { + return newSingleElementSortedSet(property.buildKeySuggestion(module, fileType, matchesRootTillMe, numOfAncestors)); + + } else if (!property.isLeaf(module)) { + + return property.findChildKeySuggestionsForQueryPrefix(module, fileType, matchesRootTillMe, + numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex, siblingsToExclude); + } + } + + return null; + } + + @Nullable + @Override + public String getDocumentationForKey(Module module, String nodeNavigationPathDotDelimited) { + return property.getDocumentationForKey(nodeNavigationPathDotDelimited); + } + + @Nullable + @Override + public SortedSet findValueSuggestionsForPrefix(Module module, FileType fileType, + List matchesRootTillMe, String prefix, + @Nullable Set siblingsToExclude) { + return property + .findSuggestionsForValues(module, fileType, matchesRootTillMe, prefix, siblingsToExclude); + } + + @Nullable + @Override + public String getDocumentationForValue(Module module, String nodeNavigationPathDotDelimited, + String originalValue) { + return property.getDocumentationForValue(module, nodeNavigationPathDotDelimited, originalValue); + } + + @Override + protected boolean isRoot() { + return parent == null; + } + + @Override + public boolean isGroup() { + return false; + } + + @Override + public boolean isProperty() { + return true; + } + + @Override + protected boolean hasOnlyOneChild(Module module) { + // since we have to delegate any further lookups to the delegate (which has additional cost associated with parsing & building childrenTrie dynamically) + // lets always lie to caller that we have more than one child so that the search terminates at this node on the initial lookup + return false; + } + + @Override + public String toTree() { + return originalName + (isRoot() ? "(root + property)" : "(property)"); + } + + @Override + public boolean removeRefCascadeDown(String containerPath) { + belongsTo.remove(containerPath); + // If the current node & all its children belong to a single file, lets remove the whole tree + return belongsTo.isEmpty(); + } + + @Override + public void refreshClassProxy(Module module) { + property.refreshDelegate(module); + } } diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/metadata/json/SpringConfigurationMetadataProperty.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/metadata/json/SpringConfigurationMetadataProperty.java index 62509a3..45030dc 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/metadata/json/SpringConfigurationMetadataProperty.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/metadata/json/SpringConfigurationMetadataProperty.java @@ -51,588 +51,591 @@ */ @EqualsAndHashCode(of = "name") public class SpringConfigurationMetadataProperty - implements Comparable { - - /** - * The full name of the PROPERTY. Names are in lower-case period-separated form (for example, server.servlet.path). This attribute is mandatory. - */ - @Setter - @Getter - private String name; - @Nullable - @Setter - @SerializedName("type") - private String className; - @Nullable - @Setter - private String description; - /** - * The class name of the source that contributed this PROPERTY. For example, if the PROPERTY were from a class annotated with @ConfigurationProperties, this attribute would contain the fully qualified name of that class. If the source type is unknown, it may be omitted. - */ - @Nullable - @Setter - private String sourceType; - /** - * Specify whether the PROPERTY is deprecated. If the field is not deprecated or if that information is not known, it may be omitted. The next table offers more detail about the springConfigurationMetadataDeprecation attribute. - */ - @Nullable - @Setter - private SpringConfigurationMetadataDeprecation deprecation; - /** - * The default value, which is used if the PROPERTY is not specified. If the type of the PROPERTY is an ARRAY, it can be an ARRAY of value(s). If the default value is unknown, it may be omitted. - */ - @Nullable - @Setter - private Object defaultValue; - - /** - * Represents either the only hint associated (or) key specific hint when the property represents a map - */ - @Nullable - @Expose(deserialize = false) - private SpringConfigurationMetadataHint genericOrKeyHint; - - /** - * If the property of type map, the property can have both keys & values. This hint represents value - */ - @Nullable - @Expose(deserialize = false) - private SpringConfigurationMetadataHint valueHint; - - /** - * Responsible for all suggestion queries that needs to be matched against a class - */ - @Nullable - private MetadataProxy delegate; - - @Nullable - private SuggestionNodeType nodeType; - private boolean delegateCreationAttempted; - - @Nullable - public List findChildDeepestKeyMatch(Module module, - List matchesRootTillParentNode, String[] pathSegments, - int pathSegmentStartIndex) { - if (!isLeaf(module)) { - if (isMapWithPredefinedKeys()) { // map - assert genericOrKeyHint != null; - String pathSegment = pathSegments[pathSegmentStartIndex]; - SpringConfigurationMetadataHintValue valueHint = - genericOrKeyHint.findHintValueWithName(pathSegment); - if (valueHint != null) { - matchesRootTillParentNode.add(new HintAwareSuggestionNode(valueHint)); - boolean lastPathSegment = pathSegmentStartIndex == pathSegments.length - 1; - if (lastPathSegment) { - return matchesRootTillParentNode; - } else { - if (!isMapWithPredefinedValues()) { - return doWithDelegateOrReturnNull(module, delegate -> delegate - .findDeepestSuggestionNode(module, matchesRootTillParentNode, pathSegments, - pathSegmentStartIndex)); - } - } - } - } else { - return doWithDelegateOrReturnNull(module, delegate -> delegate - .findDeepestSuggestionNode(module, matchesRootTillParentNode, pathSegments, - pathSegmentStartIndex)); - } - } - return null; - } - - @Nullable - public SortedSet findChildKeySuggestionsForQueryPrefix(Module module, - FileType fileType, List matchesRootTillMe, int numOfAncestors, - String[] querySegmentPrefixes, int querySegmentPrefixStartIndex, - @Nullable Set siblingsToExclude) { - boolean lastPathSegment = querySegmentPrefixStartIndex == querySegmentPrefixes.length - 1; - if (lastPathSegment && !isLeaf(module)) { - if (isMapWithPredefinedKeys()) { // map - assert genericOrKeyHint != null; - String querySegment = querySegmentPrefixes[querySegmentPrefixStartIndex]; - Collection matches = - genericOrKeyHint.findHintValuesWithPrefix(querySegment); - Stream matchesStream = - getMatchesAfterExcludingSiblings(genericOrKeyHint, matches, siblingsToExclude); - - return matchesStream.map(hintValue -> { - HintAwareSuggestionNode suggestionNode = new HintAwareSuggestionNode(hintValue); - return hintValue - .buildSuggestionForKey(fileType, matchesRootTillMe, numOfAncestors, suggestionNode, - getMapKeyType(module)); - }).collect(toCollection(TreeSet::new)); - } else { - return doWithDelegateOrReturnNull(module, delegate -> delegate - .findKeySuggestionsForQueryPrefix(module, fileType, matchesRootTillMe, numOfAncestors, - querySegmentPrefixes, querySegmentPrefixStartIndex, siblingsToExclude)); - } - } - return null; - } - - @NotNull - public Suggestion buildKeySuggestion(Module module, FileType fileType, - List matchesRootTillMe, int numOfAncestors) { - Suggestion.SuggestionBuilder builder = Suggestion.builder().suggestionToDisplay( - GenericUtil.dotDelimitedOriginalNames(matchesRootTillMe, numOfAncestors)) - .description(description).shortType(shortenedType(className)) - .defaultValue(getDefaultValueAsStr()).numOfAncestors(numOfAncestors) - .matchesTopFirst(matchesRootTillMe).icon(getSuggestionNodeType(module).getIcon()); - if (deprecation != null) { - builder.deprecationLevel(deprecation.getLevel() != null ? - deprecation.getLevel() : - SpringConfigurationMetadataDeprecationLevel.warning); - } - return builder.fileType(fileType).build(); - } - - @NotNull - public String getDocumentationForKey(String nodeNavigationPathDotDelimited) { - // Format for the documentation is as follows - /* - *

a.b.c ({@link com.acme.Generic}<{@link com.acme.Class1}, {@link com.acme.Class2}>)

- *

Default Value default value

- *

Long description

- * or of this type - *

Type {@link com.acme.Array}[]

- *

Declared at{@link com.acme.GenericRemovedClass#method}>

<-- only for groups with method info - * WARNING: - * @deprecated Due to something something. Replaced by c.d.e + implements Comparable { + + /** + * The full name of the PROPERTY. Names are in lower-case period-separated form (for example, server.servlet.path). This attribute is mandatory. + */ + @Setter + @Getter + private String name; + @Nullable + @Setter + @SerializedName("type") + private String className; + @Nullable + @Setter + private String description; + /** + * The class name of the source that contributed this PROPERTY. For example, if the PROPERTY were from a class annotated with @ConfigurationProperties, this attribute would contain the fully qualified name of that class. If the source type is unknown, it may be omitted. */ - StringBuilder builder = - new StringBuilder().append("").append(nodeNavigationPathDotDelimited).append(""); + @Nullable + @Setter + private String sourceType; + /** + * Specify whether the PROPERTY is deprecated. If the field is not deprecated or if that information is not known, it may be omitted. The next table offers more detail about the springConfigurationMetadataDeprecation attribute. + */ + @Nullable + @Setter + private SpringConfigurationMetadataDeprecation deprecation; + /** + * The default value, which is used if the PROPERTY is not specified. If the type of the PROPERTY is an ARRAY, it can be an ARRAY of value(s). If the default value is unknown, it may be omitted. + */ + @Nullable + @Setter + private Object defaultValue; - if (className != null) { - builder.append(" ("); - updateClassNameAsJavadocHtml(builder, className); - builder.append(")"); - } + /** + * Represents either the only hint associated (or) key specific hint when the property represents a map + */ + @Nullable + @Expose(deserialize = false) + private SpringConfigurationMetadataHint genericOrKeyHint; - if (description != null) { - builder.append("

").append(description).append("

"); - } + /** + * If the property of type map, the property can have both keys & values. This hint represents value + */ + @Nullable + @Expose(deserialize = false) + private SpringConfigurationMetadataHint valueHint; - if (defaultValue != null) { - builder.append("

Default value: ").append(getDefaultValueAsStr()).append("

"); - } + /** + * Responsible for all suggestion queries that needs to be matched against a class + */ + @Nullable + private MetadataProxy delegate; + + @Nullable + private SuggestionNodeType nodeType; + private boolean delegateCreationAttempted; + + @Nullable + public List findChildDeepestKeyMatch(final Module module, final List matchesRootTillParentNode, + final String[] pathSegments, int pathSegmentStartIndex) { + if (isLeaf(module)) { + return null; + } + + if (isMapWithPredefinedKeys()) { // map + assert genericOrKeyHint != null; + final String pathSegment = pathSegments[pathSegmentStartIndex]; + SpringConfigurationMetadataHintValue hint = genericOrKeyHint.findHintValueWithName(pathSegment); - if (sourceType != null) { - String sourceTypeInJavadocFormat = removeGenerics(sourceType); + if (hint != null) { + matchesRootTillParentNode.add(new HintAwareSuggestionNode(hint)); + boolean lastPathSegment = pathSegmentStartIndex == pathSegments.length - 1; - // lets show declaration point only if does not match the type - if (!sourceTypeInJavadocFormat.equals(removeGenerics(className))) { - StringBuilder buffer = new StringBuilder(); - createHyperlink(buffer, methodForDocumentationNavigation(sourceTypeInJavadocFormat), - sourceTypeInJavadocFormat, false); - sourceTypeInJavadocFormat = buffer.toString(); + if (lastPathSegment) { + return matchesRootTillParentNode; - builder.append("

Declared at ").append(sourceTypeInJavadocFormat).append("

"); - } + } else if (!isMapWithPredefinedValues()) { + return doWithDelegateOrReturnNull(module, metadataProxy -> metadataProxy + .findDeepestSuggestionNode(module, matchesRootTillParentNode, pathSegments, + pathSegmentStartIndex)); + } + } + } + + return doWithDelegateOrReturnNull(module, metadataProxy -> + metadataProxy.findDeepestSuggestionNode(module, matchesRootTillParentNode, pathSegments, pathSegmentStartIndex)); } - if (deprecation != null) { - builder.append("

").append(isDeprecatedError() ? - "ERROR: DO NOT USE THIS PROPERTY AS IT IS COMPLETELY UNSUPPORTED" : - "WARNING: PROPERTY IS DEPRECATED").append("

"); + @Nullable + public SortedSet findChildKeySuggestionsForQueryPrefix(Module module, + FileType fileType, List matchesRootTillMe, int numOfAncestors, + String[] querySegmentPrefixes, int querySegmentPrefixStartIndex, + @Nullable Set siblingsToExclude) { + boolean lastPathSegment = querySegmentPrefixStartIndex == querySegmentPrefixes.length - 1; + if (lastPathSegment && !isLeaf(module)) { + if (isMapWithPredefinedKeys()) { // map + assert genericOrKeyHint != null; + String querySegment = querySegmentPrefixes[querySegmentPrefixStartIndex]; + Collection matches = + genericOrKeyHint.findHintValuesWithPrefix(querySegment); + Stream matchesStream = + getMatchesAfterExcludingSiblings(genericOrKeyHint, matches, siblingsToExclude); + + return matchesStream.map(hintValue -> { + HintAwareSuggestionNode suggestionNode = new HintAwareSuggestionNode(hintValue); + return hintValue + .buildSuggestionForKey(fileType, matchesRootTillMe, numOfAncestors, suggestionNode, + getMapKeyType(module)); + }).collect(toCollection(TreeSet::new)); + } else { + return doWithDelegateOrReturnNull(module, metadataProxy -> metadataProxy + .findKeySuggestionsForQueryPrefix(module, fileType, matchesRootTillMe, numOfAncestors, + querySegmentPrefixes, querySegmentPrefixStartIndex, siblingsToExclude)); + } + } + return null; + } - if (deprecation.getReason() != null) { - builder.append("@deprecated Reason: ").append(deprecation.getReason()); - } + @NotNull + public Suggestion buildKeySuggestion(final Module module, final FileType fileType, final List matchesRootTillMe, final int numOfAncestors) { + + final Suggestion.SuggestionBuilder builder = Suggestion.builder() + .suggestionToDisplay(GenericUtil.dotDelimitedOriginalNames(matchesRootTillMe, numOfAncestors)) + .description(description) + .shortType(shortenedType(className)) + .defaultValue(getDefaultValueAsStr()) + .numOfAncestors(numOfAncestors) + .matchesTopFirst(matchesRootTillMe) + .icon(getSuggestionNodeType(module).getIcon()); + + if (deprecation != null) { + builder.deprecationLevel(deprecation.getLevel() != null ? deprecation.getLevel() : SpringConfigurationMetadataDeprecationLevel.warning); + } - if (deprecation.getReplacement() != null) { - builder.append("

Replaced by property ").append(deprecation.getReplacement()) - .append("

"); - } + return builder.fileType(fileType).build(); } - return builder.toString(); - } + @NotNull + public String getDocumentationForKey(String nodeNavigationPathDotDelimited) { + // Format for the documentation is as follows + /* + *

a.b.c ({@link com.acme.Generic}<{@link com.acme.Class1}, {@link com.acme.Class2}>)

+ *

Default Value default value

+ *

Long description

+ * or of this type + *

Type {@link com.acme.Array}[]

+ *

Declared at{@link com.acme.GenericRemovedClass#method}>

<-- only for groups with method info + * WARNING: + * @deprecated Due to something something. Replaced by c.d.e + */ + StringBuilder builder = + new StringBuilder().append("").append(nodeNavigationPathDotDelimited).append(""); + + if (className != null) { + builder.append(" ("); + updateClassNameAsJavadocHtml(builder, className); + builder.append(")"); + } + + if (description != null) { + builder.append("

").append(description).append("

"); + } - public boolean isLeaf(Module module) { - return isLeafWithKnownValues() || getSuggestionNodeType(module).representsLeaf() - || doWithDelegateOrReturnDefault(module, delegate -> delegate.isLeaf(module), true); - } + if (defaultValue != null) { + builder.append("

Default value: ").append(getDefaultValueAsStr()).append("

"); + } - @NotNull - public SuggestionNodeType getSuggestionNodeType(Module module) { - if (nodeType == null) { - if (className != null) { - refreshDelegate(module); + if (sourceType != null) { + String sourceTypeInJavadocFormat = removeGenerics(sourceType); - if (delegate != null) { - nodeType = delegate.getSuggestionNodeType(module); + // lets show declaration point only if does not match the type + if (!sourceTypeInJavadocFormat.equals(removeGenerics(className))) { + StringBuilder buffer = new StringBuilder(); + createHyperlink(buffer, methodForDocumentationNavigation(sourceTypeInJavadocFormat), + sourceTypeInJavadocFormat, false); + sourceTypeInJavadocFormat = buffer.toString(); + + builder.append("

Declared at ").append(sourceTypeInJavadocFormat).append("

"); + } } - if (nodeType == null) { - nodeType = UNKNOWN_CLASS; + if (deprecation != null) { + builder.append("

").append(isDeprecatedError() ? + "ERROR: DO NOT USE THIS PROPERTY AS IT IS COMPLETELY UNSUPPORTED" : + "WARNING: PROPERTY IS DEPRECATED").append("

"); + + if (deprecation.getReason() != null) { + builder.append("@deprecated Reason: ").append(deprecation.getReason()); + } + + if (deprecation.getReplacement() != null) { + builder.append("

Replaced by property ").append(deprecation.getReplacement()) + .append("

"); + } } - } else { - nodeType = SuggestionNodeType.UNDEFINED; - } - } - return nodeType; - } - - public void refreshDelegate(Module module) { - if (className != null) { - // Lets update the delegate information only if anything has changed from last time we saw this - PsiType type = getPsiType(module); - boolean validTypeExists = type != null; - // In the previous refresh, class could not be found. Now class is available in the classpath - if (validTypeExists) { - if (delegate == null) { - delegate = newMetadataProxy(module, type); - // lets force the nodeType to recalculated - nodeType = null; - } - } - // In the previous refresh, class was available in classpath. Now it is no longer available - if (!validTypeExists && delegate != null) { - delegate = null; - nodeType = UNKNOWN_CLASS; - } - } - delegateCreationAttempted = true; - } - - @Override - public int compareTo(@NotNull SpringConfigurationMetadataProperty o) { - return compare(this, o, comparing(thiz -> thiz.name)); - } - - /** - * @return true if the property is deprecated & level is error, false otherwise - */ - public boolean isDeprecatedError() { - return deprecation != null - && deprecation.getLevel() == SpringConfigurationMetadataDeprecationLevel.error; - } - - public SortedSet findSuggestionsForValues(Module module, FileType fileType, - List matchesRootTillContainerProperty, String prefix, - @Nullable Set siblingsToExclude) { - assert isLeaf(module); - if (nodeType == VALUES) { - Collection matches = - requireNonNull(genericOrKeyHint).findHintValuesWithPrefix(prefix); - if (!isEmpty(matches)) { - Stream matchesStream = - getMatchesAfterExcludingSiblings(genericOrKeyHint, matches, siblingsToExclude); - - return matchesStream.map(match -> match - .buildSuggestionForValue(fileType, matchesRootTillContainerProperty, - getDefaultValueAsStr(), getPsiType(module))).collect(toCollection(TreeSet::new)); - } - } else { - return doWithDelegateOrReturnNull(module, delegate -> delegate - .findValueSuggestionsForPrefix(module, fileType, matchesRootTillContainerProperty, prefix, - siblingsToExclude)); + return builder.toString(); } - return null; - } - - public void setGenericOrKeyHint(SpringConfigurationMetadataHint genericOrKeyHint) { - this.genericOrKeyHint = genericOrKeyHint; - updateNodeType(); - } - - public void setValueHint(SpringConfigurationMetadataHint valueHint) { - this.valueHint = valueHint; - updateNodeType(); - } - - private Stream getMatchesAfterExcludingSiblings( - @NotNull SpringConfigurationMetadataHint hintFindValueAgainst, - Collection matches, - @Nullable Set siblingsToExclude) { - Stream matchesStream; - if (siblingsToExclude != null) { - Set exclusionMembers = - siblingsToExclude.stream().map(hintFindValueAgainst::findHintValueWithName) - .collect(toSet()); - matchesStream = matches.stream().filter(value -> !exclusionMembers.contains(value)); - } else { - matchesStream = matches.stream(); - } - return matchesStream; - } - - private void updateNodeType() { - if (isMapWithPredefinedKeys() || isMapWithPredefinedValues()) { - nodeType = MAP; - } else if (isLeafWithKnownValues()) { - nodeType = VALUES; + public boolean isLeaf(Module module) { + return isLeafWithKnownValues() + || getSuggestionNodeType(module).representsLeaf() + || doWithDelegateOrReturnDefault(module, metadataProxy -> metadataProxy.isLeaf(module), true); } - } - private PsiType getPsiType(Module module) { - if (className != null) { - return safeGetValidType(module, className); - } - return null; - } - - private boolean isMapWithPredefinedValues() { - return valueHint != null && valueHint.representsValueOfMap(); - } - - private boolean isMapWithPredefinedKeys() { - return genericOrKeyHint != null && genericOrKeyHint.representsKeyOfMap(); - } - - private boolean isLeafWithKnownValues() { - return !isMapWithPredefinedKeys() && !isMapWithPredefinedValues() && genericOrKeyHint != null - && genericOrKeyHint.hasPredefinedValues(); - } - - @Contract("_, _, !null -> !null; _, _, null -> null") - private T doWithDelegateOrReturnDefault(Module module, - MetadataProxyInvokerWithReturnValue invoker, T defaultValue) { - MetadataProxy delegate = getDelegate(module); - if (delegate != null) { - return invoker.invoke(delegate); - } - return defaultValue; - } - - @Nullable - private T doWithDelegateOrReturnNull(Module module, - MetadataProxyInvokerWithReturnValue invoker) { - return doWithDelegateOrReturnDefault(module, invoker, null); - } - - private T doWithMapDelegateOrReturnNull(Module module, - MetadataProxyInvokerWithReturnValue invoker) { - MetadataProxy delegate = getDelegate(module); - if (delegate != null) { - assert delegate instanceof MapClassMetadataProxy; - return invoker.invoke(MapClassMetadataProxy.class.cast(delegate)); - } - return null; - } - - private String getDefaultValueAsStr() { - if (defaultValue != null && !(defaultValue instanceof Array) - && !(defaultValue instanceof Collection)) { - if (className != null && defaultValue instanceof Double) { - // if defaultValue is a number, its being parsed by gson as double & we will see an incorrect fraction when we take toString() - switch (className) { - case "java.lang.Integer": - return Integer.toString(((Double) defaultValue).intValue()); - case "java.lang.Byte": - return Byte.toString(((Double) defaultValue).byteValue()); - case "java.lang.Short": - return Short.toString(((Double) defaultValue).shortValue()); - } - } - return defaultValue.toString(); - } - return null; - } + @NotNull + public SuggestionNodeType getSuggestionNodeType(Module module) { + if (nodeType == null) { + if (className != null) { + refreshDelegate(module); + + if (delegate != null) { + nodeType = delegate.getSuggestionNodeType(module); + } + + if (nodeType == null) { + nodeType = UNKNOWN_CLASS; + } + } else { + nodeType = SuggestionNodeType.UNDEFINED; + } + } - @Nullable - private MetadataProxy getDelegate(Module module) { - if (!delegateCreationAttempted) { - refreshDelegate(module); - } - return delegate; - } - - @Nullable - private PsiType getMapKeyType(Module module) { - SuggestionNodeType nodeType = getSuggestionNodeType(module); - if (nodeType == MAP) { - return doWithDelegateOrReturnNull(module, delegate -> { - assert delegate instanceof MapClassMetadataProxy; - return MapClassMetadataProxy.class.cast(delegate).getMapKeyType(module); - }); + return nodeType; + } + + public void refreshDelegate(Module module) { + if (className != null) { + // Lets update the delegate information only if anything has changed from last time we saw this + PsiType type = getPsiType(module); + boolean validTypeExists = type != null; + // In the previous refresh, class could not be found. Now class is available in the classpath + if (validTypeExists) { + if (delegate == null) { + delegate = newMetadataProxy(module, type); + // lets force the nodeType to recalculated + nodeType = null; + } + } + // In the previous refresh, class was available in classpath. Now it is no longer available + if (!validTypeExists && delegate != null) { + delegate = null; + nodeType = UNKNOWN_CLASS; + } + } + delegateCreationAttempted = true; } - return null; - } - - @Nullable - private PsiType getMapValueType(Module module) { - SuggestionNodeType nodeType = getSuggestionNodeType(module); - if (nodeType == MAP) { - return doWithDelegateOrReturnNull(module, delegate -> { - assert delegate instanceof MapClassMetadataProxy; - return MapClassMetadataProxy.class.cast(delegate).getMapValueType(module); - }); + + @Override + public int compareTo(@NotNull SpringConfigurationMetadataProperty o) { + return compare(this, o, comparing(thiz -> thiz.name)); } - return null; - } - - public String getDocumentationForValue(Module module, String nodeNavigationPathDotDelimited, - String value) { - if (isLeafWithKnownValues()) { - assert genericOrKeyHint != null; - SpringConfigurationMetadataHintValue hintValueWithName = - genericOrKeyHint.findHintValueWithName(value); - if (hintValueWithName != null) { - return hintValueWithName - .getDocumentationForValue(nodeNavigationPathDotDelimited, getMapValueType(module)); - } - } else { - // possible this represents an enum - return doWithDelegateOrReturnNull(module, delegate -> delegate - .getDocumentationForValue(module, nodeNavigationPathDotDelimited, value)); + + /** + * @return true if the property is deprecated & level is error, false otherwise + */ + public boolean isDeprecatedError() { + return deprecation != null + && deprecation.getLevel() == SpringConfigurationMetadataDeprecationLevel.error; + } + + public SortedSet findSuggestionsForValues(Module module, FileType fileType, + List matchesRootTillContainerProperty, String prefix, + @Nullable Set siblingsToExclude) { + assert isLeaf(module); + if (nodeType == VALUES) { + Collection matches = + requireNonNull(genericOrKeyHint).findHintValuesWithPrefix(prefix); + if (!isEmpty(matches)) { + Stream matchesStream = + getMatchesAfterExcludingSiblings(genericOrKeyHint, matches, siblingsToExclude); + + return matchesStream.map(match -> match + .buildSuggestionForValue(fileType, matchesRootTillContainerProperty, + getDefaultValueAsStr(), getPsiType(module))).collect(toCollection(TreeSet::new)); + } + } else { + return doWithDelegateOrReturnNull(module, delegate -> delegate + .findValueSuggestionsForPrefix(module, fileType, matchesRootTillContainerProperty, prefix, + siblingsToExclude)); + } + + return null; } - return null; - } + public void setGenericOrKeyHint(SpringConfigurationMetadataHint genericOrKeyHint) { + this.genericOrKeyHint = genericOrKeyHint; + updateNodeType(); + } - class HintAwareSuggestionNode implements SuggestionNode { + public void setValueHint(SpringConfigurationMetadataHint valueHint) { + this.valueHint = valueHint; + updateNodeType(); + } - private final SpringConfigurationMetadataHintValue target; + private Stream getMatchesAfterExcludingSiblings( + @NotNull SpringConfigurationMetadataHint hintFindValueAgainst, + Collection matches, + @Nullable Set siblingsToExclude) { + Stream matchesStream; + if (siblingsToExclude != null) { + Set exclusionMembers = + siblingsToExclude.stream().map(hintFindValueAgainst::findHintValueWithName) + .collect(toSet()); + matchesStream = matches.stream().filter(value -> !exclusionMembers.contains(value)); + } else { + matchesStream = matches.stream(); + } + return matchesStream; + } - /** - * @param target hint value - */ - HintAwareSuggestionNode(SpringConfigurationMetadataHintValue target) { - this.target = target; + private void updateNodeType() { + if (isMapWithPredefinedKeys() || isMapWithPredefinedValues()) { + nodeType = MAP; + } else if (isLeafWithKnownValues()) { + nodeType = VALUES; + } } - @Nullable - @Override - public List findDeepestSuggestionNode(Module module, - List matchesRootTillParentNode, String[] pathSegments, - int pathSegmentStartIndex) { - throw new IllegalAccessError("Should never be called"); + private PsiType getPsiType(Module module) { + if (className != null) { + return safeGetValidType(module, className); + } + return null; } - @Nullable - @Override - public SortedSet findKeySuggestionsForQueryPrefix(Module module, FileType fileType, - List matchesRootTillMe, int numOfAncestors, String[] querySegmentPrefixes, - int querySegmentPrefixStartIndex) { - return doWithMapDelegateOrReturnNull(module, - delegate -> MapClassMetadataProxy.class.cast(delegate) - .findChildKeySuggestionForQueryPrefix(module, fileType, matchesRootTillMe, - numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex)); + private boolean isMapWithPredefinedValues() { + return valueHint != null && valueHint.representsValueOfMap(); } - @Nullable - @Override - public SortedSet findKeySuggestionsForQueryPrefix(Module module, FileType fileType, - List matchesRootTillMe, int numOfAncestors, String[] querySegmentPrefixes, - int querySegmentPrefixStartIndex, @Nullable Set siblingsToExclude) { - return doWithMapDelegateOrReturnNull(module, - delegate -> MapClassMetadataProxy.class.cast(delegate) - .findChildKeySuggestionForQueryPrefix(module, fileType, matchesRootTillMe, - numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex, - siblingsToExclude)); + private boolean isMapWithPredefinedKeys() { + return genericOrKeyHint != null && genericOrKeyHint.representsKeyOfMap(); } - @Override - public boolean supportsDocumentation() { - return true; + private boolean isLeafWithKnownValues() { + return !isMapWithPredefinedKeys() && !isMapWithPredefinedValues() && genericOrKeyHint != null + && genericOrKeyHint.hasPredefinedValues(); } - @Override - public String getOriginalName() { - return target.toString(); + @Contract("_, _, !null -> !null; _, _, null -> null") + private T doWithDelegateOrReturnDefault(Module module, + MetadataProxyInvokerWithReturnValue invoker, T defaultValue) { + MetadataProxy delegate = getDelegate(module); + if (delegate != null) { + return invoker.invoke(delegate); + } + return defaultValue; } @Nullable - @Override - public String getNameForDocumentation(Module module) { - return getOriginalName(); + private T doWithDelegateOrReturnNull(Module module, + MetadataProxyInvokerWithReturnValue invoker) { + return doWithDelegateOrReturnDefault(module, invoker, null); } - @Nullable - @Override - public String getDocumentationForKey(Module module, String nodeNavigationPathDotDelimited) { - return target - .getDocumentationForKey(module, nodeNavigationPathDotDelimited, getDelegate(module)); + private T doWithMapDelegateOrReturnNull(Module module, + MetadataProxyInvokerWithReturnValue invoker) { + MetadataProxy delegate = getDelegate(module); + if (delegate != null) { + assert delegate instanceof MapClassMetadataProxy; + return invoker.invoke(MapClassMetadataProxy.class.cast(delegate)); + } + return null; + } + + private String getDefaultValueAsStr() { + if (defaultValue != null && !(defaultValue instanceof Array) + && !(defaultValue instanceof Collection)) { + if (className != null && defaultValue instanceof Double) { + // if defaultValue is a number, its being parsed by gson as double & we will see an incorrect fraction when we take toString() + switch (className) { + case "java.lang.Integer": + return Integer.toString(((Double) defaultValue).intValue()); + case "java.lang.Byte": + return Byte.toString(((Double) defaultValue).byteValue()); + case "java.lang.Short": + return Short.toString(((Double) defaultValue).shortValue()); + } + } + return defaultValue.toString(); + } + return null; } @Nullable - @Override - public SortedSet findValueSuggestionsForPrefix(Module module, FileType fileType, - List matchesRootTillMe, String prefix) { - if (isMapWithPredefinedValues()) { - assert valueHint != null; - Collection matches = - valueHint.findHintValuesWithPrefix(prefix); - if (matches != null && matches.size() != 0) { - return matches.stream().map(match -> match - .buildSuggestionForValue(fileType, matchesRootTillMe, getDefaultValueAsStr(), - getMapValueType(module))).collect(toCollection(TreeSet::new)); - } - } else { - return doWithDelegateOrReturnNull(module, delegate -> delegate - .findValueSuggestionsForPrefix(module, fileType, matchesRootTillMe, prefix)); - } - return null; + private MetadataProxy getDelegate(Module module) { + if (!delegateCreationAttempted) { + refreshDelegate(module); + } + return delegate; } @Nullable - @Override - public SortedSet findValueSuggestionsForPrefix(Module module, FileType fileType, - List matchesRootTillMe, String prefix, - @Nullable Set siblingsToExclude) { - if (isMapWithPredefinedValues()) { - assert valueHint != null; - Collection matches = - valueHint.findHintValuesWithPrefix(prefix); - if (!isEmpty(matches)) { - Stream matchesStream = - getMatchesAfterExcludingSiblings(valueHint, matches, siblingsToExclude); - return matchesStream.map(match -> match - .buildSuggestionForValue(fileType, matchesRootTillMe, getDefaultValueAsStr(), - getMapValueType(module))).collect(toCollection(TreeSet::new)); - } - } else { - return doWithDelegateOrReturnNull(module, delegate -> delegate - .findValueSuggestionsForPrefix(module, fileType, matchesRootTillMe, prefix)); - } - return null; + private PsiType getMapKeyType(Module module) { + SuggestionNodeType nodeType = getSuggestionNodeType(module); + if (nodeType == MAP) { + return doWithDelegateOrReturnNull(module, delegate -> { + assert delegate instanceof MapClassMetadataProxy; + return MapClassMetadataProxy.class.cast(delegate).getMapKeyType(module); + }); + } + return null; } @Nullable - @Override - public String getDocumentationForValue(Module module, String nodeNavigationPathDotDelimited, - String originalValue) { - if (isMapWithPredefinedValues()) { - assert valueHint != null; - Collection matches = - valueHint.findHintValuesWithPrefix(originalValue); - assert matches != null && matches.size() == 1; - SpringConfigurationMetadataHintValue hint = matches.iterator().next(); - return hint - .getDocumentationForValue(nodeNavigationPathDotDelimited, getMapValueType(module)); - } else { - return doWithDelegateOrReturnNull(module, delegate -> delegate - .getDocumentationForValue(module, nodeNavigationPathDotDelimited, originalValue)); - } + private PsiType getMapValueType(Module module) { + SuggestionNodeType nodeType = getSuggestionNodeType(module); + if (nodeType == MAP) { + return doWithDelegateOrReturnNull(module, delegate -> { + assert delegate instanceof MapClassMetadataProxy; + return MapClassMetadataProxy.class.cast(delegate).getMapValueType(module); + }); + } + return null; } - @Override - public boolean isLeaf(Module module) { - if (isLeafWithKnownValues() || isMapWithPredefinedValues()) { - return true; - } - // whether the node is a leaf or not depends on the value of the map that containing property points to - return PsiCustomUtil.getSuggestionNodeType(getMapValueType(module)).representsLeaf(); + public String getDocumentationForValue(Module module, String nodeNavigationPathDotDelimited, + String value) { + if (isLeafWithKnownValues()) { + assert genericOrKeyHint != null; + SpringConfigurationMetadataHintValue hintValueWithName = + genericOrKeyHint.findHintValueWithName(value); + if (hintValueWithName != null) { + return hintValueWithName + .getDocumentationForValue(nodeNavigationPathDotDelimited, getMapValueType(module)); + } + } else { + // possible this represents an enum + return doWithDelegateOrReturnNull(module, delegate -> delegate + .getDocumentationForValue(module, nodeNavigationPathDotDelimited, value)); + } + return null; } - @Override - public boolean isMetadataNonProperty() { - return false; - } - @NotNull - @Override - public SuggestionNodeType getSuggestionNodeType(Module module) { - if (isLeafWithKnownValues() || isMapWithPredefinedValues()) { // predefined values - return ENUM; - } - return PsiCustomUtil.getSuggestionNodeType(getMapValueType(module)); - } + class HintAwareSuggestionNode implements SuggestionNode { + + private final SpringConfigurationMetadataHintValue target; + + /** + * @param target hint value + */ + HintAwareSuggestionNode(SpringConfigurationMetadataHintValue target) { + this.target = target; + } + + @Nullable + @Override + public List findDeepestSuggestionNode(Module module, + List matchesRootTillParentNode, String[] pathSegments, + int pathSegmentStartIndex) { + throw new IllegalAccessError("Should never be called"); + } + + @Nullable + @Override + public SortedSet findKeySuggestionsForQueryPrefix(Module module, FileType fileType, + List matchesRootTillMe, int numOfAncestors, String[] querySegmentPrefixes, + int querySegmentPrefixStartIndex) { + return doWithMapDelegateOrReturnNull(module, + delegate -> MapClassMetadataProxy.class.cast(delegate) + .findChildKeySuggestionForQueryPrefix(module, fileType, matchesRootTillMe, + numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex)); + } + + @Nullable + @Override + public SortedSet findKeySuggestionsForQueryPrefix(Module module, FileType fileType, + List matchesRootTillMe, int numOfAncestors, String[] querySegmentPrefixes, + int querySegmentPrefixStartIndex, @Nullable Set siblingsToExclude) { + return doWithMapDelegateOrReturnNull(module, + delegate -> MapClassMetadataProxy.class.cast(delegate) + .findChildKeySuggestionForQueryPrefix(module, fileType, matchesRootTillMe, + numOfAncestors, querySegmentPrefixes, querySegmentPrefixStartIndex, + siblingsToExclude)); + } + + @Override + public boolean supportsDocumentation() { + return true; + } + + @Override + public String getOriginalName() { + return target.toString(); + } + + @Nullable + @Override + public String getNameForDocumentation(Module module) { + return getOriginalName(); + } - } + @Nullable + @Override + public String getDocumentationForKey(Module module, String nodeNavigationPathDotDelimited) { + return target + .getDocumentationForKey(module, nodeNavigationPathDotDelimited, getDelegate(module)); + } + + @Nullable + @Override + public SortedSet findValueSuggestionsForPrefix(Module module, FileType fileType, + List matchesRootTillMe, String prefix) { + if (isMapWithPredefinedValues()) { + assert valueHint != null; + Collection matches = + valueHint.findHintValuesWithPrefix(prefix); + if (matches != null && matches.size() != 0) { + return matches.stream().map(match -> match + .buildSuggestionForValue(fileType, matchesRootTillMe, getDefaultValueAsStr(), + getMapValueType(module))).collect(toCollection(TreeSet::new)); + } + } else { + return doWithDelegateOrReturnNull(module, delegate -> delegate + .findValueSuggestionsForPrefix(module, fileType, matchesRootTillMe, prefix)); + } + return null; + } + + @Nullable + @Override + public SortedSet findValueSuggestionsForPrefix(Module module, FileType fileType, + List matchesRootTillMe, String prefix, + @Nullable Set siblingsToExclude) { + if (isMapWithPredefinedValues()) { + assert valueHint != null; + Collection matches = + valueHint.findHintValuesWithPrefix(prefix); + if (!isEmpty(matches)) { + Stream matchesStream = + getMatchesAfterExcludingSiblings(valueHint, matches, siblingsToExclude); + return matchesStream.map(match -> match + .buildSuggestionForValue(fileType, matchesRootTillMe, getDefaultValueAsStr(), + getMapValueType(module))).collect(toCollection(TreeSet::new)); + } + } else { + return doWithDelegateOrReturnNull(module, delegate -> delegate + .findValueSuggestionsForPrefix(module, fileType, matchesRootTillMe, prefix)); + } + return null; + } + + @Nullable + @Override + public String getDocumentationForValue(Module module, String nodeNavigationPathDotDelimited, + String originalValue) { + if (isMapWithPredefinedValues()) { + assert valueHint != null; + Collection matches = + valueHint.findHintValuesWithPrefix(originalValue); + assert matches != null && matches.size() == 1; + SpringConfigurationMetadataHintValue hint = matches.iterator().next(); + return hint + .getDocumentationForValue(nodeNavigationPathDotDelimited, getMapValueType(module)); + } else { + return doWithDelegateOrReturnNull(module, delegate -> delegate + .getDocumentationForValue(module, nodeNavigationPathDotDelimited, originalValue)); + } + } + + @Override + public boolean isLeaf(Module module) { + if (isLeafWithKnownValues() || isMapWithPredefinedValues()) { + return true; + } + // whether the node is a leaf or not depends on the value of the map that containing property points to + return PsiCustomUtil.getSuggestionNodeType(getMapValueType(module)).representsLeaf(); + } + + @Override + public boolean isMetadataNonProperty() { + return false; + } + + @NotNull + @Override + public SuggestionNodeType getSuggestionNodeType(Module module) { + if (isLeafWithKnownValues() || isMapWithPredefinedValues()) { // predefined values + return ENUM; + } + return PsiCustomUtil.getSuggestionNodeType(getMapValueType(module)); + } + + } } diff --git a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/service/SuggestionServiceImpl.java b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/service/SuggestionServiceImpl.java index f4fcf6e..c4dd8bb 100644 --- a/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/service/SuggestionServiceImpl.java +++ b/src/main/java/in/oneton/idea/spring/assistant/plugin/suggestion/service/SuggestionServiceImpl.java @@ -28,6 +28,7 @@ import org.apache.commons.collections4.Trie; import org.apache.commons.collections4.trie.PatriciaTrie; import org.apache.commons.lang.time.StopWatch; +import org.apache.commons.lang3.StringUtils; import javax.annotation.Nullable; import java.io.BufferedReader; @@ -59,612 +60,609 @@ public class SuggestionServiceImpl implements SuggestionService { - private static final Logger log = Logger.getInstance(SuggestionServiceImpl.class); - - private final Map> - moduleNameToSeenContainerPathToContainerInfo; - /** - * Within the trie, all keys are stored in sanitised format to enable us find keys without worrying about hiphens, underscores, e.t.c in the keys themselves - */ - private final Map> moduleNameToRootSearchIndex; - private Future currentExecution; - private volatile boolean indexingInProgress; - - SuggestionServiceImpl() { - moduleNameToSeenContainerPathToContainerInfo = new THashMap<>(); - moduleNameToRootSearchIndex = new THashMap<>(); - } - - private static String[] toSanitizedPathSegments(String element) { - String[] splits = element.trim().split(PERIOD_DELIMITER, -1); - for (int i = 0; i < splits.length; i++) { - splits[i] = sanitise(splits[i]); + private static final Logger log = Logger.getInstance(SuggestionServiceImpl.class); + + private final Map> + moduleNameToSeenContainerPathToContainerInfo; + /** + * Within the trie, all keys are stored in sanitised format to enable us find keys without worrying about hiphens, underscores, e.t.c in the keys themselves + */ + private final Map> moduleNameToRootSearchIndex; + private Future currentExecution; + private volatile boolean indexingInProgress; + + SuggestionServiceImpl() { + moduleNameToSeenContainerPathToContainerInfo = new THashMap<>(); + moduleNameToRootSearchIndex = new THashMap<>(); } - return splits; - } - private static String[] toRawPathSegments(String element) { - String[] splits = element.trim().split(PERIOD_DELIMITER, -1); - for (int i = 0; i < splits.length; i++) { - splits[i] = splits[i].trim(); + private static String[] toSanitizedPathSegments(String element) { + String[] splits = element.trim().split(PERIOD_DELIMITER, -1); + for (int i = 0; i < splits.length; i++) { + splits[i] = sanitise(splits[i]); + } + return splits; } - return splits; - } - - private static String firstPathSegment(String element) { - return element.trim().split(PERIOD_DELIMITER, -1)[0]; - } - - @Override - public void init(Project project) { - reIndex(project); - } - - @Override - public void reIndex(Project project) { - if (indexingInProgress) { - currentExecution.cancel(false); + + private static String[] toRawPathSegments(String element) { + String[] splits = element.trim().split(PERIOD_DELIMITER, -1); + for (int i = 0; i < splits.length; i++) { + splits[i] = splits[i].trim(); + } + return splits; } - //noinspection CodeBlock2Expr - currentExecution = getApplication().executeOnPooledThread(() -> { - getApplication().runReadAction(() -> { - indexingInProgress = true; - StopWatch timer = new StopWatch(); - timer.start(); - try { - debug(() -> log.debug("-> Indexing requested for project " + project.getName())); - // OrderEnumerator.orderEntries(project) is returning everything from all modules including root level module(which is called project in gradle terms) - // So, we should not be doing anything with this - - Module[] modules = ModuleManager.getInstance(project).getModules(); - for (Module module : modules) { - reindexModule(emptyList(), emptyList(), module); - } - } finally { - indexingInProgress = false; - timer.stop(); - debug(() -> log - .debug("<- Indexing took " + timer.toString() + " for project " + project.getName())); + + private static String firstPathSegment(String element) { + return element.trim().split(PERIOD_DELIMITER, -1)[0]; + } + + @Override + public void init(Project project) { + reIndex(project); + } + + @Override + public void reIndex(Project project) { + if (indexingInProgress) { + currentExecution.cancel(false); + } + //noinspection CodeBlock2Expr + currentExecution = getApplication().executeOnPooledThread(() -> getApplication() + .runReadAction(() -> { + indexingInProgress = true; + + StopWatch timer = new StopWatch(); + timer.start(); + + try { + debug(() -> log.debug("-> Indexing requested for project " + project.getName())); + // OrderEnumerator.orderEntries(project) is returning everything from all modules including root level + // module(which is called project in gradle terms) So, we should not be doing anything with this +// improvment + stream(ModuleManager.getInstance(project).getModules()) + .forEach(module -> reindexModule(emptyList(), emptyList(), module)); +// Module[] modules = ModuleManager.getInstance(project).getModules(); +// for (Module module : modules) { reindexModule(emptyList(), emptyList(), module); } + } finally { + indexingInProgress = false; + timer.stop(); + debug(() -> log.debug("<- Indexing took " + timer.toString() + " for project " + project.getName())); + } + }) + ); + } + + @Override + public void reindex(final Project project, final Module[] modules) { + if (this.indexingInProgress && currentExecution != null) { + this.currentExecution.cancel(false); + } + //noinspection CodeBlock2Expr + this.currentExecution = getApplication().executeOnPooledThread(() -> + getApplication().runReadAction(() -> { + debug(() -> log.debug("-> Indexing requested for a subset of modules of project " + project.getName())); + this.indexingInProgress = true; + + final StopWatch timer = new StopWatch(); + timer.start(); + + try { + stream(modules).forEach(module -> { + debug(() -> log.debug("--> Indexing requested for module " + module.getName())); + final StopWatch moduleTimer = new StopWatch(); + moduleTimer.start(); + + try { + reindexModule(emptyList(), emptyList(), module); + } finally { + moduleTimer.stop(); + debug(() -> log.debug("<-- Indexing took " + moduleTimer.toString() + " for module " + module.getName())); + } + }); + + } finally { + indexingInProgress = false; + timer.stop(); + debug(() -> log.debug("<- Indexing took " + timer.toString() + " for project " + project.getName())); + } + }) + ); + } + + @Override + public void reindex(Project project, Module module) { + reindex(project, new Module[]{module}); + } + + @Nullable + @Override + public List findMatchedNodesRootTillEnd(Project project, Module module, + List containerElements) { + if (moduleNameToRootSearchIndex.containsKey(module.getName())) { + String[] pathSegments = + containerElements.stream().flatMap(element -> stream(toSanitizedPathSegments(element))) + .toArray(String[]::new); + MetadataSuggestionNode searchStartNode = + moduleNameToRootSearchIndex.get(module.getName()).get(pathSegments[0]); + if (searchStartNode != null) { + List matches = modifiableList(searchStartNode); + if (pathSegments.length > 1) { + return searchStartNode.findDeepestSuggestionNode(module, matches, pathSegments, 1); + } + return matches; + } } - }); - }); - } - - @Override - public void reindex(Project project, Module[] modules) { - if (indexingInProgress) { - if (currentExecution != null) { - currentExecution.cancel(false); - } + return null; } - //noinspection CodeBlock2Expr - currentExecution = getApplication().executeOnPooledThread(() -> { - getApplication().runReadAction(() -> { - debug(() -> log.debug( - "-> Indexing requested for a subset of modules of project " + project.getName())); - indexingInProgress = true; + + @Override + public boolean canProvideSuggestions(Project project, Module module) { + Trie rootSearchIndex = + moduleNameToRootSearchIndex.get(module.getName()); + return rootSearchIndex != null && rootSearchIndex.size() != 0; + } + + @Override + public List findSuggestionsForQueryPrefix(Project project, Module module, + FileType fileType, PsiElement element, @Nullable List ancestralKeys, + String queryWithDotDelimitedPrefixes, @Nullable Set siblingsToExclude) { + return doFindSuggestionsForQueryPrefix(module, + moduleNameToRootSearchIndex.get(module.getName()), fileType, element, ancestralKeys, + queryWithDotDelimitedPrefixes, siblingsToExclude); + } + + private List computeNewContainersToProcess(OrderEnumerator orderEnumerator, + Map seenContainerPathToContainerInfo) { + List containersToProcess = new ArrayList<>(); + for (VirtualFile metadataFileContainer : orderEnumerator.recursively().classes().getRoots()) { + Collection metadataContainerInfos = + MetadataContainerInfo.newInstances(metadataFileContainer); + for (MetadataContainerInfo metadataContainerInfo : metadataContainerInfos) { + boolean seenBefore = seenContainerPathToContainerInfo + .containsKey(metadataContainerInfo.getContainerArchiveOrFileRef()); + + boolean updatedSinceLastSeen = false; + if (seenBefore) { + MetadataContainerInfo seenMetadataContainerInfo = seenContainerPathToContainerInfo + .get(metadataContainerInfo.getContainerArchiveOrFileRef()); + updatedSinceLastSeen = metadataContainerInfo.isModified(seenMetadataContainerInfo); + if (updatedSinceLastSeen) { + debug(() -> log.debug("Container seems to have been updated. Previous version: " + + seenMetadataContainerInfo + "; Newer version: " + metadataContainerInfo)); + } + } + + boolean looksFresh = !seenBefore || updatedSinceLastSeen; + boolean processMetadata = looksFresh && metadataContainerInfo.containsMetadataFile(); + if (processMetadata) { + containersToProcess.add(metadataContainerInfo); + } + + if (looksFresh) { + seenContainerPathToContainerInfo + .put(metadataContainerInfo.getContainerArchiveOrFileRef(), metadataContainerInfo); + } + } + } + + if (containersToProcess.size() == 0) { + debug(() -> log.debug("No (new)metadata files to index")); + } + return containersToProcess; + } + + private List doFindSuggestionsForQueryPrefix(Module module, + Trie rootSearchIndex, FileType fileType, PsiElement element, + @Nullable List ancestralKeys, String queryWithDotDelimitedPrefixes, + @Nullable Set siblingsToExclude) { + debug(() -> log.debug("Search requested for " + queryWithDotDelimitedPrefixes)); StopWatch timer = new StopWatch(); timer.start(); try { - for (Module module : modules) { - debug(() -> log.debug("--> Indexing requested for module " + module.getName())); - StopWatch moduleTimer = new StopWatch(); - moduleTimer.start(); - try { - reindexModule(emptyList(), emptyList(), module); - } finally { - moduleTimer.stop(); - debug(() -> log.debug( - "<-- Indexing took " + moduleTimer.toString() + " for module " + module - .getName())); + String[] querySegmentPrefixes = toSanitizedPathSegments(queryWithDotDelimitedPrefixes); + Set suggestions = null; + if (ancestralKeys != null) { + String[] ancestralKeySegments = + ancestralKeys.stream().flatMap(key -> stream(toRawPathSegments(key))) + .toArray(String[]::new); + MetadataSuggestionNode rootNode = rootSearchIndex.get(sanitise(ancestralKeySegments[0])); + if (rootNode != null) { + List matchesRootToDeepest; + SuggestionNode startSearchFrom = null; + if (ancestralKeySegments.length > 1) { + String[] sanitisedAncestralPathSegments = + stream(ancestralKeySegments).map(SuggestionNode::sanitise).toArray(String[]::new); + matchesRootToDeepest = rootNode + .findDeepestSuggestionNode(module, modifiableList(rootNode), + sanitisedAncestralPathSegments, 1); + if (matchesRootToDeepest != null && matchesRootToDeepest.size() != 0) { + startSearchFrom = matchesRootToDeepest.get(matchesRootToDeepest.size() - 1); + } + } else { + startSearchFrom = rootNode; + matchesRootToDeepest = singletonList(rootNode); + } + + if (startSearchFrom != null) { + // if search start node is a leaf, this means, the user is looking for values for the given key, lets find the suggestions for values + if (startSearchFrom.isLeaf(module)) { + suggestions = startSearchFrom.findValueSuggestionsForPrefix(module, fileType, + unmodifiableList(matchesRootToDeepest), + sanitise(truncateIdeaDummyIdentifier(element.getText())), siblingsToExclude); + } else { + suggestions = startSearchFrom.findKeySuggestionsForQueryPrefix(module, fileType, + unmodifiableList(matchesRootToDeepest), matchesRootToDeepest.size(), + querySegmentPrefixes, 0, siblingsToExclude); + } + } + } + } else { + String rootQuerySegmentPrefix = querySegmentPrefixes[0]; + SortedMap topLevelQueryResults = + rootSearchIndex.prefixMap(rootQuerySegmentPrefix); + + Collection childNodes; + int querySegmentPrefixStartIndex; + + // If no results are found at the top level, let dive deeper and find matches + if (topLevelQueryResults == null || topLevelQueryResults.size() == 0) { + childNodes = rootSearchIndex.values(); + querySegmentPrefixStartIndex = 0; + } else { + childNodes = topLevelQueryResults.values(); + querySegmentPrefixStartIndex = 1; + } + + Collection nodesToSearchAgainst; + if (siblingsToExclude != null) { + Set nodesToExclude = siblingsToExclude.stream() + .flatMap(exclude -> rootSearchIndex.prefixMap(exclude).values().stream()) + .collect(toSet()); + nodesToSearchAgainst = + childNodes.stream().filter(node -> !nodesToExclude.contains(node)).collect(toList()); + } else { + nodesToSearchAgainst = childNodes; + } + + suggestions = doFindSuggestionsForQueryPrefix(module, fileType, nodesToSearchAgainst, + querySegmentPrefixes, querySegmentPrefixStartIndex); } - } + + if (suggestions != null) { + return toLookupElementBuilders(suggestions); + } + return null; } finally { - indexingInProgress = false; - timer.stop(); - debug(() -> log - .debug("<- Indexing took " + timer.toString() + " for project " + project.getName())); - } - }); - }); - } - - @Override - public void reindex(Project project, Module module) { - reindex(project, new Module[] {module}); - } - - @Nullable - @Override - public List findMatchedNodesRootTillEnd(Project project, Module module, - List containerElements) { - if (moduleNameToRootSearchIndex.containsKey(module.getName())) { - String[] pathSegments = - containerElements.stream().flatMap(element -> stream(toSanitizedPathSegments(element))) - .toArray(String[]::new); - MetadataSuggestionNode searchStartNode = - moduleNameToRootSearchIndex.get(module.getName()).get(pathSegments[0]); - if (searchStartNode != null) { - List matches = modifiableList(searchStartNode); - if (pathSegments.length > 1) { - return searchStartNode.findDeepestSuggestionNode(module, matches, pathSegments, 1); + timer.stop(); + debug(() -> log.debug("Search took " + timer.toString())); } - return matches; - } } - return null; - } - - @Override - public boolean canProvideSuggestions(Project project, Module module) { - Trie rootSearchIndex = - moduleNameToRootSearchIndex.get(module.getName()); - return rootSearchIndex != null && rootSearchIndex.size() != 0; - } - - @Override - public List findSuggestionsForQueryPrefix(Project project, Module module, - FileType fileType, PsiElement element, @Nullable List ancestralKeys, - String queryWithDotDelimitedPrefixes, @Nullable Set siblingsToExclude) { - return doFindSuggestionsForQueryPrefix(module, - moduleNameToRootSearchIndex.get(module.getName()), fileType, element, ancestralKeys, - queryWithDotDelimitedPrefixes, siblingsToExclude); - } - - private List computeNewContainersToProcess(OrderEnumerator orderEnumerator, - Map seenContainerPathToContainerInfo) { - List containersToProcess = new ArrayList<>(); - for (VirtualFile metadataFileContainer : orderEnumerator.recursively().classes().getRoots()) { - Collection metadataContainerInfos = - MetadataContainerInfo.newInstances(metadataFileContainer); - for (MetadataContainerInfo metadataContainerInfo : metadataContainerInfos) { - boolean seenBefore = seenContainerPathToContainerInfo - .containsKey(metadataContainerInfo.getContainerArchiveOrFileRef()); - - boolean updatedSinceLastSeen = false; - if (seenBefore) { - MetadataContainerInfo seenMetadataContainerInfo = seenContainerPathToContainerInfo - .get(metadataContainerInfo.getContainerArchiveOrFileRef()); - updatedSinceLastSeen = metadataContainerInfo.isModified(seenMetadataContainerInfo); - if (updatedSinceLastSeen) { - debug(() -> log.debug("Container seems to have been updated. Previous version: " - + seenMetadataContainerInfo + "; Newer version: " + metadataContainerInfo)); - } - } - boolean looksFresh = !seenBefore || updatedSinceLastSeen; - boolean processMetadata = looksFresh && metadataContainerInfo.containsMetadataFile(); - if (processMetadata) { - containersToProcess.add(metadataContainerInfo); + @Nullable + private Set doFindSuggestionsForQueryPrefix(Module module, FileType fileType, + Collection nodesToSearchWithin, String[] querySegmentPrefixes, + int querySegmentPrefixStartIndex) { + Set suggestions = null; + for (MetadataSuggestionNode suggestionNode : nodesToSearchWithin) { + Set matchedSuggestions = suggestionNode + .findKeySuggestionsForQueryPrefix(module, fileType, modifiableList(suggestionNode), 0, + querySegmentPrefixes, querySegmentPrefixStartIndex); + if (matchedSuggestions != null) { + if (suggestions == null) { + suggestions = new THashSet<>(); + } + suggestions.addAll(matchedSuggestions); + } } + return suggestions; + } - if (looksFresh) { - seenContainerPathToContainerInfo - .put(metadataContainerInfo.getContainerArchiveOrFileRef(), metadataContainerInfo); + @Nullable + private List toLookupElementBuilders( + @Nullable Set suggestions) { + if (suggestions != null) { + return suggestions.stream().map(Suggestion::newLookupElement).collect(toList()); } - } + return null; } - if (containersToProcess.size() == 0) { - debug(() -> log.debug("No (new)metadata files to index")); + /** + * Finds the containers that are not reachable from current classpath + * + * @param orderEnumerator classpath roots to work with + * @param seenContainerPathToContainerInfo seen container paths + * @return list of container paths that are no longer valid + */ + private List computeContainersToRemove(OrderEnumerator orderEnumerator, + Map seenContainerPathToContainerInfo) { + Set newContainerPaths = stream(orderEnumerator.recursively().classes().getRoots()) + .flatMap(MetadataContainerInfo::getContainerArchiveOrFileRefs).collect(toSet()); + Set knownContainerPathSet = new THashSet<>(seenContainerPathToContainerInfo.keySet()); + knownContainerPathSet.removeAll(newContainerPaths); + return knownContainerPathSet.stream().map(seenContainerPathToContainerInfo::get) + .collect(toList()); } - return containersToProcess; - } - - private List doFindSuggestionsForQueryPrefix(Module module, - Trie rootSearchIndex, FileType fileType, PsiElement element, - @Nullable List ancestralKeys, String queryWithDotDelimitedPrefixes, - @Nullable Set siblingsToExclude) { - debug(() -> log.debug("Search requested for " + queryWithDotDelimitedPrefixes)); - StopWatch timer = new StopWatch(); - timer.start(); - try { - String[] querySegmentPrefixes = toSanitizedPathSegments(queryWithDotDelimitedPrefixes); - Set suggestions = null; - if (ancestralKeys != null) { - String[] ancestralKeySegments = - ancestralKeys.stream().flatMap(key -> stream(toRawPathSegments(key))) - .toArray(String[]::new); - MetadataSuggestionNode rootNode = rootSearchIndex.get(sanitise(ancestralKeySegments[0])); - if (rootNode != null) { - List matchesRootToDeepest; - SuggestionNode startSearchFrom = null; - if (ancestralKeySegments.length > 1) { - String[] sanitisedAncestralPathSegments = - stream(ancestralKeySegments).map(SuggestionNode::sanitise).toArray(String[]::new); - matchesRootToDeepest = rootNode - .findDeepestSuggestionNode(module, modifiableList(rootNode), - sanitisedAncestralPathSegments, 1); - if (matchesRootToDeepest != null && matchesRootToDeepest.size() != 0) { - startSearchFrom = matchesRootToDeepest.get(matchesRootToDeepest.size() - 1); + + private void processContainers(final Module module, + final List containersToProcess, + final List containersToRemove, + final Map seenContainerPathToContainerInfo, + final Trie rootSearchIndex) { + // Lets remove references to files that are no longer present in classpath + containersToRemove.forEach(container -> removeReferences(seenContainerPathToContainerInfo, rootSearchIndex, container)); + + for (MetadataContainerInfo metadataContainerInfo : containersToProcess) { + // lets remove existing references from search index, as these files are modified, so that we can rebuild index + if (seenContainerPathToContainerInfo.containsKey(metadataContainerInfo.getContainerArchiveOrFileRef())) { + removeReferences(seenContainerPathToContainerInfo, rootSearchIndex, metadataContainerInfo); } - } else { - startSearchFrom = rootNode; - matchesRootToDeepest = singletonList(rootNode); - } - - if (startSearchFrom != null) { - // if search start node is a leaf, this means, the user is looking for values for the given key, lets find the suggestions for values - if (startSearchFrom.isLeaf(module)) { - suggestions = startSearchFrom.findValueSuggestionsForPrefix(module, fileType, - unmodifiableList(matchesRootToDeepest), - sanitise(truncateIdeaDummyIdentifier(element.getText())), siblingsToExclude); - } else { - suggestions = startSearchFrom.findKeySuggestionsForQueryPrefix(module, fileType, - unmodifiableList(matchesRootToDeepest), matchesRootToDeepest.size(), - querySegmentPrefixes, 0, siblingsToExclude); + + final String metadataFilePath = metadataContainerInfo.getFileUrl(); + + try (final var inputStream = metadataContainerInfo.getMetadataFile().getInputStream()) { + final var springConfigurationMetadata = new GsonBuilder() + // register custom mapper adapters + .registerTypeAdapter(SpringConfigurationMetadataValueProviderType.class, + new SpringConfigurationMetadataValueProviderTypeDeserializer()) + .registerTypeAdapterFactory(new GsonPostProcessEnablingTypeFactory()) + .create() + .fromJson(new BufferedReader(new InputStreamReader(inputStream)), SpringConfigurationMetadata.class); + + buildMetadataHierarchy(module, rootSearchIndex, metadataContainerInfo, springConfigurationMetadata); + + seenContainerPathToContainerInfo.put(metadataContainerInfo.getContainerArchiveOrFileRef(), metadataContainerInfo); + + } catch (final IOException e) { + log.error("Exception encountered while processing metadata file: " + metadataFilePath, e); + removeReferences(seenContainerPathToContainerInfo, rootSearchIndex, metadataContainerInfo); } - } - } - } else { - String rootQuerySegmentPrefix = querySegmentPrefixes[0]; - SortedMap topLevelQueryResults = - rootSearchIndex.prefixMap(rootQuerySegmentPrefix); - - Collection childNodes; - int querySegmentPrefixStartIndex; - - // If no results are found at the top level, let dive deeper and find matches - if (topLevelQueryResults == null || topLevelQueryResults.size() == 0) { - childNodes = rootSearchIndex.values(); - querySegmentPrefixStartIndex = 0; - } else { - childNodes = topLevelQueryResults.values(); - querySegmentPrefixStartIndex = 1; } + } - Collection nodesToSearchAgainst; - if (siblingsToExclude != null) { - Set nodesToExclude = siblingsToExclude.stream() - .flatMap(exclude -> rootSearchIndex.prefixMap(exclude).values().stream()) - .collect(toSet()); - nodesToSearchAgainst = - childNodes.stream().filter(node -> !nodesToExclude.contains(node)).collect(toList()); - } else { - nodesToSearchAgainst = childNodes; - } + private void reindexModule(List newProjectSourcesToProcess, + List projectContainersToRemove, Module module) { - suggestions = doFindSuggestionsForQueryPrefix(module, fileType, nodesToSearchAgainst, - querySegmentPrefixes, querySegmentPrefixStartIndex); - } - - if (suggestions != null) { - return toLookupElementBuilders(suggestions); - } - return null; - } finally { - timer.stop(); - debug(() -> log.debug("Search took " + timer.toString())); - } - } - - @Nullable - private Set doFindSuggestionsForQueryPrefix(Module module, FileType fileType, - Collection nodesToSearchWithin, String[] querySegmentPrefixes, - int querySegmentPrefixStartIndex) { - Set suggestions = null; - for (MetadataSuggestionNode suggestionNode : nodesToSearchWithin) { - Set matchedSuggestions = suggestionNode - .findKeySuggestionsForQueryPrefix(module, fileType, modifiableList(suggestionNode), 0, - querySegmentPrefixes, querySegmentPrefixStartIndex); - if (matchedSuggestions != null) { - if (suggestions == null) { - suggestions = new THashSet<>(); + final var moduleSeenContainerPathToSeenContainerInfo = moduleNameToSeenContainerPathToContainerInfo + .computeIfAbsent(module.getName(), k -> new THashMap<>()); + + var moduleRootSearchIndex = moduleNameToRootSearchIndex.get(module.getName()); + if (moduleRootSearchIndex == null) { + moduleRootSearchIndex = new PatriciaTrie<>(); + moduleNameToRootSearchIndex.put(module.getName(), moduleRootSearchIndex); } - suggestions.addAll(matchedSuggestions); - } - } - return suggestions; - } - - @Nullable - private List toLookupElementBuilders( - @Nullable Set suggestions) { - if (suggestions != null) { - return suggestions.stream().map(Suggestion::newLookupElement).collect(toList()); + + final OrderEnumerator moduleOrderEnumerator = OrderEnumerator.orderEntries(module); + + final List newModuleContainersToProcess = computeNewContainersToProcess(moduleOrderEnumerator, + moduleSeenContainerPathToSeenContainerInfo); + newModuleContainersToProcess.addAll(newProjectSourcesToProcess); + + final List moduleContainersToRemove = computeContainersToRemove(moduleOrderEnumerator, + moduleSeenContainerPathToSeenContainerInfo); + moduleContainersToRemove.addAll(projectContainersToRemove); + + processContainers(module, newModuleContainersToProcess, moduleContainersToRemove, + moduleSeenContainerPathToSeenContainerInfo, moduleRootSearchIndex); } - return null; - } - - /** - * Finds the containers that are not reachable from current classpath - * - * @param orderEnumerator classpath roots to work with - * @param seenContainerPathToContainerInfo seen container paths - * @return list of container paths that are no longer valid - */ - private List computeContainersToRemove(OrderEnumerator orderEnumerator, - Map seenContainerPathToContainerInfo) { - Set newContainerPaths = stream(orderEnumerator.recursively().classes().getRoots()) - .flatMap(MetadataContainerInfo::getContainerArchiveOrFileRefs).collect(toSet()); - Set knownContainerPathSet = new THashSet<>(seenContainerPathToContainerInfo.keySet()); - knownContainerPathSet.removeAll(newContainerPaths); - return knownContainerPathSet.stream().map(seenContainerPathToContainerInfo::get) - .collect(toList()); - } - - private void processContainers(Module module, List containersToProcess, - List containersToRemove, - Map seenContainerPathToContainerInfo, - Trie rootSearchIndex) { - // Lets remove references to files that are no longer present in classpath - containersToRemove.forEach( - container -> removeReferences(seenContainerPathToContainerInfo, rootSearchIndex, - container)); - - for (MetadataContainerInfo metadataContainerInfo : containersToProcess) { - // lets remove existing references from search index, as these files are modified, so that we can rebuild index - if (seenContainerPathToContainerInfo - .containsKey(metadataContainerInfo.getContainerArchiveOrFileRef())) { - removeReferences(seenContainerPathToContainerInfo, rootSearchIndex, metadataContainerInfo); - } - - String metadataFilePath = metadataContainerInfo.getFileUrl(); - try (InputStream inputStream = metadataContainerInfo.getMetadataFile().getInputStream()) { - GsonBuilder gsonBuilder = new GsonBuilder(); - // register custom mapper adapters - gsonBuilder.registerTypeAdapter(SpringConfigurationMetadataValueProviderType.class, - new SpringConfigurationMetadataValueProviderTypeDeserializer()); - gsonBuilder.registerTypeAdapterFactory(new GsonPostProcessEnablingTypeFactory()); - SpringConfigurationMetadata springConfigurationMetadata = gsonBuilder.create() - .fromJson(new BufferedReader(new InputStreamReader(inputStream)), - SpringConfigurationMetadata.class); - buildMetadataHierarchy(module, rootSearchIndex, metadataContainerInfo, - springConfigurationMetadata); - - seenContainerPathToContainerInfo - .put(metadataContainerInfo.getContainerArchiveOrFileRef(), metadataContainerInfo); - } catch (IOException e) { - log.error("Exception encountered while processing metadata file: " + metadataFilePath, e); - removeReferences(seenContainerPathToContainerInfo, rootSearchIndex, metadataContainerInfo); - } + + private void buildMetadataHierarchy(Module module, + Trie rootSearchIndex, + MetadataContainerInfo metadataContainerInfo, + SpringConfigurationMetadata springConfigurationMetadata) { + debug(() -> log.debug("Adding container to index " + metadataContainerInfo)); + + final String containerPath = metadataContainerInfo.getContainerArchiveOrFileRef(); + addGroupsToIndex(module, rootSearchIndex, springConfigurationMetadata, containerPath); + addPropertiesToIndex(module, rootSearchIndex, springConfigurationMetadata, containerPath); + addHintsToIndex(module, rootSearchIndex, springConfigurationMetadata, containerPath); + + debug(() -> log.debug("Done adding container to index")); } - } - - private void reindexModule(List newProjectSourcesToProcess, - List projectContainersToRemove, Module module) { - Map moduleSeenContainerPathToSeenContainerInfo = - moduleNameToSeenContainerPathToContainerInfo - .computeIfAbsent(module.getName(), k -> new THashMap<>()); - - Trie moduleRootSearchIndex = - moduleNameToRootSearchIndex.get(module.getName()); - if (moduleRootSearchIndex == null) { - moduleRootSearchIndex = new PatriciaTrie<>(); - moduleNameToRootSearchIndex.put(module.getName(), moduleRootSearchIndex); + + private void addHintsToIndex(Module module, Trie rootSearchIndex, + SpringConfigurationMetadata springConfigurationMetadata, String containerPath) { + List hints = springConfigurationMetadata.getHints(); + if (hints != null) { + hints.sort(comparing(SpringConfigurationMetadataHint::getName)); + for (SpringConfigurationMetadataHint hint : hints) { + String[] pathSegments = toSanitizedPathSegments(hint.getExpectedPropertyName()); + MetadataSuggestionNode closestMetadata = + findDeepestMetadataMatch(rootSearchIndex, pathSegments, true); + if (closestMetadata != null) { + if (!closestMetadata.isProperty()) { + log.warn( + "Unexpected hint " + hint.getName() + " is assigned to group " + closestMetadata + .getPathFromRoot(module) + + " found. Hints can be only assigned to property. Ignoring the hint completely.Existing group belongs to (" + + closestMetadata.getBelongsTo().stream().collect(joining(",")) + + "), New hint belongs " + containerPath); + } else { + MetadataPropertySuggestionNode propertySuggestionNode = + MetadataPropertySuggestionNode.class.cast(closestMetadata); + if (hint.representsValueOfMap()) { + propertySuggestionNode.getProperty().setValueHint(hint); + } else { + propertySuggestionNode.getProperty().setGenericOrKeyHint(hint); + } + } + } + } + } } - OrderEnumerator moduleOrderEnumerator = OrderEnumerator.orderEntries(module); - - List newModuleContainersToProcess = - computeNewContainersToProcess(moduleOrderEnumerator, - moduleSeenContainerPathToSeenContainerInfo); - newModuleContainersToProcess.addAll(newProjectSourcesToProcess); - - List moduleContainersToRemove = - computeContainersToRemove(moduleOrderEnumerator, - moduleSeenContainerPathToSeenContainerInfo); - moduleContainersToRemove.addAll(projectContainersToRemove); - - processContainers(module, newModuleContainersToProcess, moduleContainersToRemove, - moduleSeenContainerPathToSeenContainerInfo, moduleRootSearchIndex); - } - - private void buildMetadataHierarchy(Module module, - Trie rootSearchIndex, - MetadataContainerInfo metadataContainerInfo, - SpringConfigurationMetadata springConfigurationMetadata) { - debug(() -> log.debug("Adding container to index " + metadataContainerInfo)); - String containerPath = metadataContainerInfo.getContainerArchiveOrFileRef(); - addGroupsToIndex(module, rootSearchIndex, springConfigurationMetadata, containerPath); - addPropertiesToIndex(module, rootSearchIndex, springConfigurationMetadata, containerPath); - addHintsToIndex(module, rootSearchIndex, springConfigurationMetadata, containerPath); - debug(() -> log.debug("Done adding container to index")); - } - - private void addHintsToIndex(Module module, Trie rootSearchIndex, - SpringConfigurationMetadata springConfigurationMetadata, String containerPath) { - List hints = springConfigurationMetadata.getHints(); - if (hints != null) { - hints.sort(comparing(SpringConfigurationMetadataHint::getName)); - for (SpringConfigurationMetadataHint hint : hints) { - String[] pathSegments = toSanitizedPathSegments(hint.getExpectedPropertyName()); - MetadataSuggestionNode closestMetadata = - findDeepestMetadataMatch(rootSearchIndex, pathSegments, true); - if (closestMetadata != null) { - if (!closestMetadata.isProperty()) { - log.warn( - "Unexpected hint " + hint.getName() + " is assigned to group " + closestMetadata - .getPathFromRoot(module) - + " found. Hints can be only assigned to property. Ignoring the hint completely.Existing group belongs to (" - + closestMetadata.getBelongsTo().stream().collect(joining(",")) - + "), New hint belongs " + containerPath); - } else { - MetadataPropertySuggestionNode propertySuggestionNode = - MetadataPropertySuggestionNode.class.cast(closestMetadata); - if (hint.representsValueOfMap()) { - propertySuggestionNode.getProperty().setValueHint(hint); + private void addPropertiesToIndex(Module module, + Trie rootSearchIndex, + SpringConfigurationMetadata springConfigurationMetadata, String containerArchiveOrFileRef) { + List properties = + springConfigurationMetadata.getProperties(); + properties.sort(comparing(SpringConfigurationMetadataProperty::getName)); + for (SpringConfigurationMetadataProperty property : properties) { + String[] pathSegments = toSanitizedPathSegments(property.getName()); + String[] rawPathSegments = toRawPathSegments(property.getName()); + MetadataSuggestionNode closestMetadata = + findDeepestMetadataMatch(rootSearchIndex, pathSegments, false); + + int startIndex; + if (closestMetadata == null) { // path does not have a corresponding root element + boolean onlyRootSegmentExists = pathSegments.length == 1; + if (onlyRootSegmentExists) { + closestMetadata = MetadataPropertySuggestionNode + .newInstance(rawPathSegments[0], property, null, containerArchiveOrFileRef); + } else { + closestMetadata = MetadataNonPropertySuggestionNode + .newInstance(rawPathSegments[0], null, containerArchiveOrFileRef); + } + rootSearchIndex.put(pathSegments[0], closestMetadata); + + // since we already handled the root level item, let addChildren start from index 1 of pathSegments + startIndex = 1; } else { - propertySuggestionNode.getProperty().setGenericOrKeyHint(hint); + startIndex = closestMetadata.numOfHopesToRoot() + 1; + } + + boolean haveMoreSegmentsLeft = startIndex < rawPathSegments.length; + + if (haveMoreSegmentsLeft) { + if (!closestMetadata.isProperty()) { + MetadataNonPropertySuggestionNode.class.cast(closestMetadata) + .addChildren(property, rawPathSegments, startIndex, containerArchiveOrFileRef); + } else { + log.warn("Detected conflict between a new group & existing property for suggestion path " + + closestMetadata.getPathFromRoot(module) + + ". Ignoring property. Existing non property node belongs to (" + closestMetadata + .getBelongsTo().stream().collect(joining(",")) + "), New property belongs to " + + containerArchiveOrFileRef); + } + } else { + if (!closestMetadata.isProperty()) { + log.warn( + "Detected conflict between a new metadata property & existing non property node for suggestion path " + + closestMetadata.getPathFromRoot(module) + + ". Ignoring property. Existing non property node belongs to (" + closestMetadata + .getBelongsTo().stream().collect(joining(",")) + "), New property belongs to " + + containerArchiveOrFileRef); + } else { + closestMetadata.addRefCascadeTillRoot(containerArchiveOrFileRef); + log.debug("Detected a duplicate metadata property for suggestion path " + closestMetadata + .getPathFromRoot(module) + ". Ignoring property. Existing property belongs to (" + + closestMetadata.getBelongsTo().stream().collect(joining(",")) + + "), New property belongs to " + containerArchiveOrFileRef); + } } - } } - } } - } - - private void addPropertiesToIndex(Module module, - Trie rootSearchIndex, - SpringConfigurationMetadata springConfigurationMetadata, String containerArchiveOrFileRef) { - List properties = - springConfigurationMetadata.getProperties(); - properties.sort(comparing(SpringConfigurationMetadataProperty::getName)); - for (SpringConfigurationMetadataProperty property : properties) { - String[] pathSegments = toSanitizedPathSegments(property.getName()); - String[] rawPathSegments = toRawPathSegments(property.getName()); - MetadataSuggestionNode closestMetadata = - findDeepestMetadataMatch(rootSearchIndex, pathSegments, false); - - int startIndex; - if (closestMetadata == null) { // path does not have a corresponding root element - boolean onlyRootSegmentExists = pathSegments.length == 1; - if (onlyRootSegmentExists) { - closestMetadata = MetadataPropertySuggestionNode - .newInstance(rawPathSegments[0], property, null, containerArchiveOrFileRef); - } else { - closestMetadata = MetadataNonPropertySuggestionNode - .newInstance(rawPathSegments[0], null, containerArchiveOrFileRef); - } - rootSearchIndex.put(pathSegments[0], closestMetadata); - - // since we already handled the root level item, let addChildren start from index 1 of pathSegments - startIndex = 1; - } else { - startIndex = closestMetadata.numOfHopesToRoot() + 1; - } - - boolean haveMoreSegmentsLeft = startIndex < rawPathSegments.length; - - if (haveMoreSegmentsLeft) { - if (!closestMetadata.isProperty()) { - MetadataNonPropertySuggestionNode.class.cast(closestMetadata) - .addChildren(property, rawPathSegments, startIndex, containerArchiveOrFileRef); - } else { - log.warn("Detected conflict between a new group & existing property for suggestion path " - + closestMetadata.getPathFromRoot(module) - + ". Ignoring property. Existing non property node belongs to (" + closestMetadata - .getBelongsTo().stream().collect(joining(",")) + "), New property belongs to " - + containerArchiveOrFileRef); - } - } else { - if (!closestMetadata.isProperty()) { - log.warn( - "Detected conflict between a new metadata property & existing non property node for suggestion path " - + closestMetadata.getPathFromRoot(module) - + ". Ignoring property. Existing non property node belongs to (" + closestMetadata - .getBelongsTo().stream().collect(joining(",")) + "), New property belongs to " - + containerArchiveOrFileRef); - } else { - closestMetadata.addRefCascadeTillRoot(containerArchiveOrFileRef); - log.debug("Detected a duplicate metadata property for suggestion path " + closestMetadata - .getPathFromRoot(module) + ". Ignoring property. Existing property belongs to (" - + closestMetadata.getBelongsTo().stream().collect(joining(",")) - + "), New property belongs to " + containerArchiveOrFileRef); + + private void addGroupsToIndex(Module module, Trie rootSearchIndex, + SpringConfigurationMetadata springConfigurationMetadata, String containerArchiveOrFileRef) { + final var springConfigurationMetadataGroups = springConfigurationMetadata.getGroups(); + + if (springConfigurationMetadataGroups == null) { + return; } - } + + springConfigurationMetadataGroups.sort(comparing(SpringConfigurationMetadataGroup::getName)); + + springConfigurationMetadataGroups.forEach(springConfigurationMetadataGroup -> { + final String[] pathSegments = toSanitizedPathSegments(springConfigurationMetadataGroup.getName()); + final String[] rawPathSegments = toRawPathSegments(springConfigurationMetadataGroup.getName()); + + MetadataSuggestionNode closestMetadata = findDeepestMetadataMatch(rootSearchIndex, pathSegments, false); + + int startIndex; + if (closestMetadata == null) { // path does not have a corresponding root element + // lets build just the root element. Rest of the path segments will be taken care of by the addChildren method + boolean onlyRootSegmentExists = pathSegments.length == 1; + MetadataNonPropertySuggestionNode newGroupSuggestionNode = MetadataNonPropertySuggestionNode + .newInstance(rawPathSegments[0], null, containerArchiveOrFileRef); + + if (onlyRootSegmentExists) { + newGroupSuggestionNode.setGroup(module, springConfigurationMetadataGroup); + } + + rootSearchIndex.put(pathSegments[0], newGroupSuggestionNode); + closestMetadata = newGroupSuggestionNode; + // since we already handled the root level item, let addChildren start from index 1 of pathSegments + startIndex = 1; + } else { + startIndex = closestMetadata.numOfHopesToRoot() + 1; + } + + if (closestMetadata.isProperty()) { + log.warn("Detected conflict between an existing metadata property & new springConfigurationMetadataGroup for suggestion path " + + closestMetadata.getPathFromRoot(module) + + ". Ignoring new springConfigurationMetadataGroup. Existing Property belongs to (" + + String.join(",", closestMetadata.getBelongsTo()) +// + closestMetadata.getBelongsTo().stream().collect(joining(",")) + + "), New Group belongs to " + containerArchiveOrFileRef); + } else { + // lets add container as a reference till root + final var groupSuggestionNode = (MetadataNonPropertySuggestionNode) closestMetadata; + groupSuggestionNode.addRefCascadeTillRoot(containerArchiveOrFileRef); + + final boolean haveMoreSegmentsLeft = startIndex < rawPathSegments.length; + if (haveMoreSegmentsLeft) { + groupSuggestionNode.addChildren(module, springConfigurationMetadataGroup, rawPathSegments, startIndex, containerArchiveOrFileRef); + } else { + // Node is an intermediate node that has neither springConfigurationMetadataGroup nor property assigned to it, lets assign this springConfigurationMetadataGroup to it + // Can happen when `a.b.c` is already added to the metadata tree from an earlier metadata source & now we are trying to add a springConfigurationMetadataGroup for `a.b` + // In this e.g, startIndex would be 2. So, there is no point in adding children. We only need to update the tree appropriately + groupSuggestionNode.setGroup(module, springConfigurationMetadataGroup); + } + } + }); } - } - - private void addGroupsToIndex(Module module, Trie rootSearchIndex, - SpringConfigurationMetadata springConfigurationMetadata, String containerArchiveOrFileRef) { - List groups = springConfigurationMetadata.getGroups(); - if (groups != null) { - groups.sort(comparing(SpringConfigurationMetadataGroup::getName)); - for (SpringConfigurationMetadataGroup group : groups) { - String[] pathSegments = toSanitizedPathSegments(group.getName()); - String[] rawPathSegments = toRawPathSegments(group.getName()); - - MetadataSuggestionNode closestMetadata = MetadataSuggestionNode.class - .cast(findDeepestMetadataMatch(rootSearchIndex, pathSegments, false)); - - int startIndex; - if (closestMetadata == null) { // path does not have a corresponding root element - // lets build just the root element. Rest of the path segments will be taken care of by the addChildren method - boolean onlyRootSegmentExists = pathSegments.length == 1; - MetadataNonPropertySuggestionNode newGroupSuggestionNode = - MetadataNonPropertySuggestionNode - .newInstance(rawPathSegments[0], null, containerArchiveOrFileRef); - if (onlyRootSegmentExists) { - newGroupSuggestionNode.setGroup(module, group); - } - rootSearchIndex.put(pathSegments[0], newGroupSuggestionNode); - - closestMetadata = newGroupSuggestionNode; - // since we already handled the root level item, let addChildren start from index 1 of pathSegments - startIndex = 1; - } else { - startIndex = closestMetadata.numOfHopesToRoot() + 1; - } - if (closestMetadata.isProperty()) { - log.warn( - "Detected conflict between an existing metadata property & new group for suggestion path " - + closestMetadata.getPathFromRoot(module) - + ". Ignoring new group. Existing Property belongs to (" + closestMetadata - .getBelongsTo().stream().collect(joining(",")) + "), New Group belongs to " - + containerArchiveOrFileRef); - } else { - // lets add container as a reference till root - MetadataNonPropertySuggestionNode groupSuggestionNode = - MetadataNonPropertySuggestionNode.class.cast(closestMetadata); - groupSuggestionNode.addRefCascadeTillRoot(containerArchiveOrFileRef); - - boolean haveMoreSegmentsLeft = startIndex < rawPathSegments.length; - if (haveMoreSegmentsLeft) { - groupSuggestionNode - .addChildren(module, group, rawPathSegments, startIndex, containerArchiveOrFileRef); - } else { - // Node is an intermediate node that has neither group nor property assigned to it, lets assign this group to it - // Can happen when `a.b.c` is already added to the metadata tree from an earlier metadata source & now we are trying to add a group for `a.b` - // In this e.g, startIndex would be 2. So, there is no point in adding children. We only need to update the tree appropriately - groupSuggestionNode.setGroup(module, group); - } + private MetadataSuggestionNode findDeepestMetadataMatch(final Map roots, + final String[] pathSegments, + final boolean matchAllSegments) { + final MetadataSuggestionNode closestMatchedRoot = roots.get(pathSegments[0]); + return closestMatchedRoot == null ? null + : closestMatchedRoot.findDeepestMetadataNode(pathSegments, 1, matchAllSegments); + } + + private void removeReferences(final Map containerPathToContainerInfo, + final Trie rootSearchIndex, + final MetadataContainerInfo metadataContainerInfo) { + debug(() -> log.debug("Removing references to " + metadataContainerInfo)); + + final String containerPath = metadataContainerInfo.getContainerArchiveOrFileRef(); + containerPathToContainerInfo.remove(containerPath); + + final Iterator searchIndexIterator = rootSearchIndex.keySet().iterator(); + + while (searchIndexIterator.hasNext()) { + MetadataSuggestionNode suggestionNode = rootSearchIndex.get(searchIndexIterator.next()); + if (suggestionNode != null && suggestionNode.removeRefCascadeDown(metadataContainerInfo.getContainerArchiveOrFileRef())) { + searchIndexIterator.remove(); + } } - } + } - } - - private MetadataSuggestionNode findDeepestMetadataMatch(Map roots, - String[] pathSegments, boolean matchAllSegments) { - MetadataSuggestionNode closestMatchedRoot = roots.get(pathSegments[0]); - if (closestMatchedRoot != null) { - closestMatchedRoot = - closestMatchedRoot.findDeepestMetadataNode(pathSegments, 1, matchAllSegments); + + @SuppressWarnings("unused") + private String toTree() { + final StringBuilder builder = new StringBuilder(); + moduleNameToRootSearchIndex.forEach((key, value) -> { + builder.append("Module: ") + .append(key) + .append(StringUtils.LF); + + value.values().forEach(root -> builder + .append(root.toTree().trim().replaceAll("^", " ").replaceAll(StringUtils.LF, "\n ")) + .append(StringUtils.LF)); + }); + return builder.toString(); } - return closestMatchedRoot; - } - - private void removeReferences(Map containerPathToContainerInfo, - Trie rootSearchIndex, - MetadataContainerInfo metadataContainerInfo) { - debug(() -> log.debug("Removing references to " + metadataContainerInfo)); - String containerPath = metadataContainerInfo.getContainerArchiveOrFileRef(); - containerPathToContainerInfo.remove(containerPath); - - Iterator searchIndexIterator = rootSearchIndex.keySet().iterator(); - while (searchIndexIterator.hasNext()) { - SuggestionNode root = rootSearchIndex.get(searchIndexIterator.next()); - if (root != null) { - boolean removeTree = MetadataSuggestionNode.class.cast(root) - .removeRefCascadeDown(metadataContainerInfo.getContainerArchiveOrFileRef()); - if (removeTree) { - searchIndexIterator.remove(); + + /** + * Debug logging can be enabled by adding fully classified class name/package name with # prefix + * For eg., to enable debug logging, go `Help > Debug log settings` & type `#in.oneton.idea.spring.assistant.plugin.suggestion.service.SuggestionServiceImpl` + * + * @param doWhenDebug code to execute when debug is enabled + */ + private void debug(final Runnable doWhenDebug) { + if (log.isDebugEnabled()) { + doWhenDebug.run(); } - } - } - } - - @SuppressWarnings("unused") - private String toTree() { - StringBuilder builder = new StringBuilder(); - moduleNameToRootSearchIndex.forEach((k, v) -> { - builder.append("Module: ").append(k).append("\n"); - v.values().forEach(root -> builder - .append(root.toTree().trim().replaceAll("^", " ").replaceAll("\n", "\n ")) - .append("\n")); - }); - return builder.toString(); - } - - /** - * Debug logging can be enabled by adding fully classified class name/package name with # prefix - * For eg., to enable debug logging, go `Help > Debug log settings` & type `#in.oneton.idea.spring.assistant.plugin.suggestion.service.SuggestionServiceImpl` - * - * @param doWhenDebug code to execute when debug is enabled - */ - private void debug(Runnable doWhenDebug) { - if (log.isDebugEnabled()) { - doWhenDebug.run(); } - } } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 3967ee6..6bf1625 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -1,23 +1,37 @@ - + in.1ton.idea.spring.assistant.plugin Spring Assistant - - Development Team @ 1Ton Technologies + + eltonsandre and Development Team @ 1Ton Technologies + - - + + Autocomplete wizard for configuration property with spring + application.(properties/yaml); - + Fix and refactor: IC-2020.3.+ + ]]>] + + + ] + com.intellij.modules.java com.intellij.modules.lang org.jetbrains.plugins.yaml com.intellij.properties org.jetbrains.idea.maven - org.jetbrains.plugins.gradle + com.intellij.gradle + + + +