diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..adec706 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,1288 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = false +max_line_length = 120 +tab_width = 4 +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 = +ij_wrap_on_typing = false + +[*.css] +ij_css_align_closing_brace_with_properties = false +ij_css_blank_lines_around_nested_selector = 1 +ij_css_blank_lines_between_blocks = 1 +ij_css_block_comment_add_space = false +ij_css_brace_placement = end_of_line +ij_css_enforce_quotes_on_format = false +ij_css_hex_color_long_format = false +ij_css_hex_color_lower_case = false +ij_css_hex_color_short_format = false +ij_css_hex_color_upper_case = false +ij_css_keep_blank_lines_in_code = 2 +ij_css_keep_indents_on_empty_lines = false +ij_css_keep_single_line_blocks = false +ij_css_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_css_space_after_colon = true +ij_css_space_before_opening_brace = true +ij_css_use_double_quotes = true +ij_css_value_alignment = do_not_align + +[*.dcl] +ij_declarative_keep_indents_on_empty_lines = false + +[*.java] +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_deconstruction_list_components = true +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_align_types_in_multi_catch = true +ij_java_annotation_new_line_in_record_component = 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_field_with_annotations = 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_blank_lines_between_record_components = 0 +ij_java_block_brace_style = end_of_line +ij_java_block_comment_add_space = false +ij_java_block_comment_at_first_column = true +ij_java_builder_methods = +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_count_to_use_import_on_demand = 5 +ij_java_class_names_in_javadoc = 1 +ij_java_deconstruction_list_wrap = normal +ij_java_delete_unused_module_imports = false +ij_java_do_not_indent_top_level_class_members = false +ij_java_do_not_wrap_after_single_annotation = false +ij_java_do_not_wrap_after_single_annotation_in_parameter = 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_entity_dd_prefix = +ij_java_entity_dd_suffix = EJB +ij_java_entity_eb_prefix = +ij_java_entity_eb_suffix = Bean +ij_java_entity_hi_prefix = +ij_java_entity_hi_suffix = Home +ij_java_entity_lhi_prefix = Local +ij_java_entity_lhi_suffix = Home +ij_java_entity_li_prefix = Local +ij_java_entity_li_suffix = +ij_java_entity_pk_class = java.lang.String +ij_java_entity_ri_prefix = +ij_java_entity_ri_suffix = +ij_java_entity_vo_prefix = +ij_java_entity_vo_suffix = VO +ij_java_enum_constants_wrap = off +ij_java_enum_field_annotation_wrap = off +ij_java_extends_keyword_wrap = off +ij_java_extends_list_wrap = off +ij_java_field_annotation_wrap = split_into_lines +ij_java_field_name_prefix = +ij_java_field_name_suffix = +ij_java_filter_class_prefix = +ij_java_filter_class_suffix = +ij_java_filter_dd_prefix = +ij_java_filter_dd_suffix = +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 = false +ij_java_generate_use_type_annotation_before_type = true +ij_java_if_brace_force = never +ij_java_imports_layout = *,|,javax.**,java.**,|,$* +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_builder_methods_indents = false +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_layout_on_demand_import_from_same_package_first = true +ij_java_layout_static_imports_separately = true +ij_java_line_comment_add_space = false +ij_java_line_comment_add_space_on_reformat = false +ij_java_line_comment_at_first_column = true +ij_java_listener_class_prefix = +ij_java_listener_class_suffix = +ij_java_local_variable_name_prefix = +ij_java_local_variable_name_suffix = +ij_java_message_dd_prefix = +ij_java_message_dd_suffix = EJB +ij_java_message_eb_prefix = +ij_java_message_eb_suffix = Bean +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_multi_catch_types_wrap = normal +ij_java_names_count_to_use_import_on_demand = 3 +ij_java_new_line_after_lparen_in_annotation = false +ij_java_new_line_after_lparen_in_deconstruction_pattern = true +ij_java_new_line_after_lparen_in_record_header = false +ij_java_new_line_when_body_is_presented = false +ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.* +ij_java_parameter_annotation_wrap = off +ij_java_parameter_name_prefix = +ij_java_parameter_name_suffix = +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_preserve_module_imports = true +ij_java_record_components_wrap = normal +ij_java_repeat_annotations = +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_annotation = false +ij_java_rparen_on_new_line_in_deconstruction_pattern = true +ij_java_rparen_on_new_line_in_record_header = false +ij_java_servlet_class_prefix = +ij_java_servlet_class_suffix = +ij_java_servlet_dd_prefix = +ij_java_servlet_dd_suffix = +ij_java_session_dd_prefix = +ij_java_session_dd_suffix = EJB +ij_java_session_eb_prefix = +ij_java_session_eb_suffix = Bean +ij_java_session_hi_prefix = +ij_java_session_hi_suffix = Home +ij_java_session_lhi_prefix = Local +ij_java_session_lhi_suffix = Home +ij_java_session_li_prefix = Local +ij_java_session_li_suffix = +ij_java_session_ri_prefix = +ij_java_session_ri_suffix = +ij_java_session_si_prefix = +ij_java_session_si_suffix = Service +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_deconstruction_list = 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_annotation_eq = 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_inside_block_braces_when_body_is_present = 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_deconstruction_list = 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_static_field_name_prefix = +ij_java_static_field_name_suffix = +ij_java_subclass_name_prefix = +ij_java_subclass_name_suffix = Impl +ij_java_switch_expressions_wrap = normal +ij_java_ternary_operation_signs_on_next_line = false +ij_java_ternary_operation_wrap = off +ij_java_test_name_prefix = +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 +ij_java_wrap_semicolon_after_call_chain = false + +[*.less] +indent_size = 2 +ij_less_align_closing_brace_with_properties = false +ij_less_blank_lines_around_nested_selector = 1 +ij_less_blank_lines_between_blocks = 1 +ij_less_block_comment_add_space = false +ij_less_brace_placement = 0 +ij_less_enforce_quotes_on_format = false +ij_less_hex_color_long_format = false +ij_less_hex_color_lower_case = false +ij_less_hex_color_short_format = false +ij_less_hex_color_upper_case = false +ij_less_keep_blank_lines_in_code = 2 +ij_less_keep_indents_on_empty_lines = false +ij_less_keep_single_line_blocks = false +ij_less_line_comment_add_space = false +ij_less_line_comment_at_first_column = false +ij_less_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_less_space_after_colon = true +ij_less_space_before_opening_brace = true +ij_less_use_double_quotes = true +ij_less_value_alignment = 0 + +[*.nbtt] +indent_style = tab +max_line_length = 150 +ij_continuation_indent_size = 4 +ij_nbtt_array_wrapping = 1 +ij_nbtt_keep_indents_on_empty_lines = false +ij_nbtt_list_wrapping = 1 +ij_nbtt_space_after_colon = true +ij_nbtt_space_after_comma = true +ij_nbtt_space_before_colon = false +ij_nbtt_space_before_comma = false +ij_nbtt_spaces_within_brackets = false +ij_nbtt_spaces_within_parentheses = false + +[*.proto] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_protobuf_keep_blank_lines_in_code = 2 +ij_protobuf_keep_indents_on_empty_lines = false +ij_protobuf_keep_line_breaks = true +ij_protobuf_space_after_comma = true +ij_protobuf_space_before_comma = false +ij_protobuf_spaces_around_assignment_operators = true +ij_protobuf_spaces_within_braces = false +ij_protobuf_spaces_within_brackets = false + +[*.sass] +indent_size = 2 +ij_sass_align_closing_brace_with_properties = false +ij_sass_blank_lines_around_nested_selector = 1 +ij_sass_blank_lines_between_blocks = 1 +ij_sass_brace_placement = 0 +ij_sass_enforce_quotes_on_format = false +ij_sass_hex_color_long_format = false +ij_sass_hex_color_lower_case = false +ij_sass_hex_color_short_format = false +ij_sass_hex_color_upper_case = false +ij_sass_keep_blank_lines_in_code = 2 +ij_sass_keep_indents_on_empty_lines = false +ij_sass_keep_single_line_blocks = false +ij_sass_line_comment_add_space = false +ij_sass_line_comment_at_first_column = false +ij_sass_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_sass_space_after_colon = true +ij_sass_space_before_opening_brace = true +ij_sass_use_double_quotes = true +ij_sass_value_alignment = 0 + +[*.scss] +indent_size = 2 +ij_scss_align_closing_brace_with_properties = false +ij_scss_blank_lines_around_nested_selector = 1 +ij_scss_blank_lines_between_blocks = 1 +ij_scss_block_comment_add_space = false +ij_scss_brace_placement = 0 +ij_scss_enforce_quotes_on_format = false +ij_scss_hex_color_long_format = false +ij_scss_hex_color_lower_case = false +ij_scss_hex_color_short_format = false +ij_scss_hex_color_upper_case = false +ij_scss_keep_blank_lines_in_code = 2 +ij_scss_keep_indents_on_empty_lines = false +ij_scss_keep_single_line_blocks = false +ij_scss_line_comment_add_space = false +ij_scss_line_comment_at_first_column = false +ij_scss_properties_order = font,font-family,font-size,font-weight,font-style,font-variant,font-size-adjust,font-stretch,line-height,position,z-index,top,right,bottom,left,display,visibility,float,clear,overflow,overflow-x,overflow-y,clip,zoom,align-content,align-items,align-self,flex,flex-flow,flex-basis,flex-direction,flex-grow,flex-shrink,flex-wrap,justify-content,order,box-sizing,width,min-width,max-width,height,min-height,max-height,margin,margin-top,margin-right,margin-bottom,margin-left,padding,padding-top,padding-right,padding-bottom,padding-left,table-layout,empty-cells,caption-side,border-spacing,border-collapse,list-style,list-style-position,list-style-type,list-style-image,content,quotes,counter-reset,counter-increment,resize,cursor,user-select,nav-index,nav-up,nav-right,nav-down,nav-left,transition,transition-delay,transition-timing-function,transition-duration,transition-property,transform,transform-origin,animation,animation-name,animation-duration,animation-play-state,animation-timing-function,animation-delay,animation-iteration-count,animation-direction,text-align,text-align-last,vertical-align,white-space,text-decoration,text-emphasis,text-emphasis-color,text-emphasis-style,text-emphasis-position,text-indent,text-justify,letter-spacing,word-spacing,text-outline,text-transform,text-wrap,text-overflow,text-overflow-ellipsis,text-overflow-mode,word-wrap,word-break,tab-size,hyphens,pointer-events,opacity,color,border,border-width,border-style,border-color,border-top,border-top-width,border-top-style,border-top-color,border-right,border-right-width,border-right-style,border-right-color,border-bottom,border-bottom-width,border-bottom-style,border-bottom-color,border-left,border-left-width,border-left-style,border-left-color,border-radius,border-top-left-radius,border-top-right-radius,border-bottom-right-radius,border-bottom-left-radius,border-image,border-image-source,border-image-slice,border-image-width,border-image-outset,border-image-repeat,outline,outline-width,outline-style,outline-color,outline-offset,background,background-color,background-image,background-repeat,background-attachment,background-position,background-position-x,background-position-y,background-clip,background-origin,background-size,box-decoration-break,box-shadow,text-shadow +ij_scss_space_after_colon = true +ij_scss_space_before_opening_brace = true +ij_scss_use_double_quotes = true +ij_scss_value_alignment = 0 + +[*.vue] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_vue_indent_children_of_top_level = template +ij_vue_interpolation_new_line_after_start_delimiter = true +ij_vue_interpolation_new_line_before_end_delimiter = true +ij_vue_interpolation_wrap = off +ij_vue_keep_indents_on_empty_lines = false +ij_vue_spaces_within_interpolation_expressions = true + +[.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 + +[{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.pom,*.rng,*.tld,*.wadl,*.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_add_space = false +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 + +[{*.ats,*.cts,*.mts,*.ts}] +ij_continuation_indent_size = 4 +ij_typescript_align_imports = false +ij_typescript_align_multiline_array_initializer_expression = false +ij_typescript_align_multiline_binary_operation = false +ij_typescript_align_multiline_chained_methods = false +ij_typescript_align_multiline_extends_list = false +ij_typescript_align_multiline_for = true +ij_typescript_align_multiline_parameters = true +ij_typescript_align_multiline_parameters_in_calls = false +ij_typescript_align_multiline_ternary_operation = false +ij_typescript_align_object_properties = 0 +ij_typescript_align_union_types = false +ij_typescript_align_var_statements = 0 +ij_typescript_array_initializer_new_line_after_left_brace = false +ij_typescript_array_initializer_right_brace_on_new_line = false +ij_typescript_array_initializer_wrap = off +ij_typescript_assignment_wrap = off +ij_typescript_binary_operation_sign_on_next_line = false +ij_typescript_binary_operation_wrap = off +ij_typescript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_typescript_blank_lines_after_imports = 1 +ij_typescript_blank_lines_around_class = 1 +ij_typescript_blank_lines_around_field = 0 +ij_typescript_blank_lines_around_field_in_interface = 0 +ij_typescript_blank_lines_around_function = 1 +ij_typescript_blank_lines_around_method = 1 +ij_typescript_blank_lines_around_method_in_interface = 1 +ij_typescript_block_brace_style = end_of_line +ij_typescript_block_comment_add_space = false +ij_typescript_block_comment_at_first_column = true +ij_typescript_call_parameters_new_line_after_left_paren = false +ij_typescript_call_parameters_right_paren_on_new_line = false +ij_typescript_call_parameters_wrap = off +ij_typescript_catch_on_new_line = false +ij_typescript_chained_call_dot_on_new_line = true +ij_typescript_class_brace_style = end_of_line +ij_typescript_class_decorator_wrap = split_into_lines +ij_typescript_class_field_decorator_wrap = off +ij_typescript_class_method_decorator_wrap = off +ij_typescript_comma_on_new_line = false +ij_typescript_do_while_brace_force = never +ij_typescript_else_on_new_line = false +ij_typescript_enforce_trailing_comma = keep +ij_typescript_enum_constants_wrap = on_every_item +ij_typescript_extends_keyword_wrap = off +ij_typescript_extends_list_wrap = off +ij_typescript_field_prefix = _ +ij_typescript_file_name_style = relaxed +ij_typescript_finally_on_new_line = false +ij_typescript_for_brace_force = never +ij_typescript_for_statement_new_line_after_left_paren = false +ij_typescript_for_statement_right_paren_on_new_line = false +ij_typescript_for_statement_wrap = off +ij_typescript_force_quote_style = false +ij_typescript_force_semicolon_style = false +ij_typescript_function_expression_brace_style = end_of_line +ij_typescript_function_parameter_decorator_wrap = off +ij_typescript_if_brace_force = never +ij_typescript_import_merge_members = global +ij_typescript_import_prefer_absolute_path = global +ij_typescript_import_sort_members = true +ij_typescript_import_sort_module_name = false +ij_typescript_import_use_node_resolution = true +ij_typescript_imports_wrap = on_every_item +ij_typescript_indent_case_from_switch = true +ij_typescript_indent_chained_calls = true +ij_typescript_indent_package_children = 0 +ij_typescript_jsdoc_include_types = false +ij_typescript_jsx_attribute_value = braces +ij_typescript_keep_blank_lines_in_code = 2 +ij_typescript_keep_first_column_comment = true +ij_typescript_keep_indents_on_empty_lines = false +ij_typescript_keep_line_breaks = true +ij_typescript_keep_simple_blocks_in_one_line = false +ij_typescript_keep_simple_methods_in_one_line = false +ij_typescript_line_comment_add_space = true +ij_typescript_line_comment_at_first_column = false +ij_typescript_method_brace_style = end_of_line +ij_typescript_method_call_chain_wrap = off +ij_typescript_method_parameters_new_line_after_left_paren = false +ij_typescript_method_parameters_right_paren_on_new_line = false +ij_typescript_method_parameters_wrap = off +ij_typescript_object_literal_wrap = on_every_item +ij_typescript_object_types_wrap = on_every_item +ij_typescript_parentheses_expression_new_line_after_left_paren = false +ij_typescript_parentheses_expression_right_paren_on_new_line = false +ij_typescript_place_assignment_sign_on_next_line = false +ij_typescript_prefer_as_type_cast = false +ij_typescript_prefer_explicit_types_function_expression_returns = false +ij_typescript_prefer_explicit_types_function_returns = false +ij_typescript_prefer_explicit_types_vars_fields = false +ij_typescript_prefer_parameters_wrap = false +ij_typescript_property_prefix = +ij_typescript_reformat_c_style_comments = false +ij_typescript_space_after_colon = true +ij_typescript_space_after_comma = true +ij_typescript_space_after_dots_in_rest_parameter = false +ij_typescript_space_after_generator_mult = true +ij_typescript_space_after_property_colon = true +ij_typescript_space_after_quest = true +ij_typescript_space_after_type_colon = true +ij_typescript_space_after_unary_not = false +ij_typescript_space_before_async_arrow_lparen = true +ij_typescript_space_before_catch_keyword = true +ij_typescript_space_before_catch_left_brace = true +ij_typescript_space_before_catch_parentheses = true +ij_typescript_space_before_class_lbrace = true +ij_typescript_space_before_class_left_brace = true +ij_typescript_space_before_colon = true +ij_typescript_space_before_comma = false +ij_typescript_space_before_do_left_brace = true +ij_typescript_space_before_else_keyword = true +ij_typescript_space_before_else_left_brace = true +ij_typescript_space_before_finally_keyword = true +ij_typescript_space_before_finally_left_brace = true +ij_typescript_space_before_for_left_brace = true +ij_typescript_space_before_for_parentheses = true +ij_typescript_space_before_for_semicolon = false +ij_typescript_space_before_function_left_parenth = true +ij_typescript_space_before_generator_mult = false +ij_typescript_space_before_if_left_brace = true +ij_typescript_space_before_if_parentheses = true +ij_typescript_space_before_method_call_parentheses = false +ij_typescript_space_before_method_left_brace = true +ij_typescript_space_before_method_parentheses = false +ij_typescript_space_before_property_colon = false +ij_typescript_space_before_quest = true +ij_typescript_space_before_switch_left_brace = true +ij_typescript_space_before_switch_parentheses = true +ij_typescript_space_before_try_left_brace = true +ij_typescript_space_before_type_colon = false +ij_typescript_space_before_unary_not = false +ij_typescript_space_before_while_keyword = true +ij_typescript_space_before_while_left_brace = true +ij_typescript_space_before_while_parentheses = true +ij_typescript_spaces_around_additive_operators = true +ij_typescript_spaces_around_arrow_function_operator = true +ij_typescript_spaces_around_assignment_operators = true +ij_typescript_spaces_around_bitwise_operators = true +ij_typescript_spaces_around_equality_operators = true +ij_typescript_spaces_around_logical_operators = true +ij_typescript_spaces_around_multiplicative_operators = true +ij_typescript_spaces_around_relational_operators = true +ij_typescript_spaces_around_shift_operators = true +ij_typescript_spaces_around_unary_operator = false +ij_typescript_spaces_within_array_initializer_brackets = false +ij_typescript_spaces_within_brackets = false +ij_typescript_spaces_within_catch_parentheses = false +ij_typescript_spaces_within_for_parentheses = false +ij_typescript_spaces_within_if_parentheses = false +ij_typescript_spaces_within_imports = false +ij_typescript_spaces_within_interpolation_expressions = false +ij_typescript_spaces_within_method_call_parentheses = false +ij_typescript_spaces_within_method_parentheses = false +ij_typescript_spaces_within_object_literal_braces = false +ij_typescript_spaces_within_object_type_braces = true +ij_typescript_spaces_within_parentheses = false +ij_typescript_spaces_within_switch_parentheses = false +ij_typescript_spaces_within_type_assertion = false +ij_typescript_spaces_within_union_types = true +ij_typescript_spaces_within_while_parentheses = false +ij_typescript_special_else_if_treatment = true +ij_typescript_ternary_operation_signs_on_next_line = false +ij_typescript_ternary_operation_wrap = off +ij_typescript_union_types_wrap = on_every_item +ij_typescript_use_chained_calls_group_indents = false +ij_typescript_use_double_quotes = true +ij_typescript_use_explicit_js_extension = auto +ij_typescript_use_import_type = auto +ij_typescript_use_path_mapping = always +ij_typescript_use_public_modifier = false +ij_typescript_use_semicolon_after_statement = true +ij_typescript_var_declaration_wrap = normal +ij_typescript_while_brace_force = never +ij_typescript_while_on_new_line = false +ij_typescript_wrap_comments = 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 +ij_shell_use_unix_line_separator = true + +[{*.cjs,*.es6,*.js,*.mjs}] +ij_continuation_indent_size = 4 +ij_javascript_align_imports = false +ij_javascript_align_multiline_array_initializer_expression = false +ij_javascript_align_multiline_binary_operation = false +ij_javascript_align_multiline_chained_methods = false +ij_javascript_align_multiline_extends_list = false +ij_javascript_align_multiline_for = true +ij_javascript_align_multiline_parameters = true +ij_javascript_align_multiline_parameters_in_calls = false +ij_javascript_align_multiline_ternary_operation = false +ij_javascript_align_object_properties = 0 +ij_javascript_align_union_types = false +ij_javascript_align_var_statements = 0 +ij_javascript_array_initializer_new_line_after_left_brace = false +ij_javascript_array_initializer_right_brace_on_new_line = false +ij_javascript_array_initializer_wrap = off +ij_javascript_assignment_wrap = off +ij_javascript_binary_operation_sign_on_next_line = false +ij_javascript_binary_operation_wrap = off +ij_javascript_blacklist_imports = rxjs/Rx,node_modules/**,**/node_modules/**,@angular/material,@angular/material/typings/** +ij_javascript_blank_lines_after_imports = 1 +ij_javascript_blank_lines_around_class = 1 +ij_javascript_blank_lines_around_field = 0 +ij_javascript_blank_lines_around_function = 1 +ij_javascript_blank_lines_around_method = 1 +ij_javascript_block_brace_style = end_of_line +ij_javascript_block_comment_add_space = false +ij_javascript_block_comment_at_first_column = true +ij_javascript_call_parameters_new_line_after_left_paren = false +ij_javascript_call_parameters_right_paren_on_new_line = false +ij_javascript_call_parameters_wrap = off +ij_javascript_catch_on_new_line = false +ij_javascript_chained_call_dot_on_new_line = true +ij_javascript_class_brace_style = end_of_line +ij_javascript_class_decorator_wrap = split_into_lines +ij_javascript_class_field_decorator_wrap = off +ij_javascript_class_method_decorator_wrap = off +ij_javascript_comma_on_new_line = false +ij_javascript_do_while_brace_force = never +ij_javascript_else_on_new_line = false +ij_javascript_enforce_trailing_comma = keep +ij_javascript_extends_keyword_wrap = off +ij_javascript_extends_list_wrap = off +ij_javascript_field_prefix = _ +ij_javascript_file_name_style = relaxed +ij_javascript_finally_on_new_line = false +ij_javascript_for_brace_force = never +ij_javascript_for_statement_new_line_after_left_paren = false +ij_javascript_for_statement_right_paren_on_new_line = false +ij_javascript_for_statement_wrap = off +ij_javascript_force_quote_style = false +ij_javascript_force_semicolon_style = false +ij_javascript_function_expression_brace_style = end_of_line +ij_javascript_function_parameter_decorator_wrap = off +ij_javascript_if_brace_force = never +ij_javascript_import_merge_members = global +ij_javascript_import_prefer_absolute_path = global +ij_javascript_import_sort_members = true +ij_javascript_import_sort_module_name = false +ij_javascript_import_use_node_resolution = true +ij_javascript_imports_wrap = on_every_item +ij_javascript_indent_case_from_switch = true +ij_javascript_indent_chained_calls = true +ij_javascript_indent_package_children = 0 +ij_javascript_jsx_attribute_value = braces +ij_javascript_keep_blank_lines_in_code = 2 +ij_javascript_keep_first_column_comment = true +ij_javascript_keep_indents_on_empty_lines = false +ij_javascript_keep_line_breaks = true +ij_javascript_keep_simple_blocks_in_one_line = false +ij_javascript_keep_simple_methods_in_one_line = false +ij_javascript_line_comment_add_space = true +ij_javascript_line_comment_at_first_column = false +ij_javascript_method_brace_style = end_of_line +ij_javascript_method_call_chain_wrap = off +ij_javascript_method_parameters_new_line_after_left_paren = false +ij_javascript_method_parameters_right_paren_on_new_line = false +ij_javascript_method_parameters_wrap = off +ij_javascript_object_literal_wrap = on_every_item +ij_javascript_object_types_wrap = on_every_item +ij_javascript_parentheses_expression_new_line_after_left_paren = false +ij_javascript_parentheses_expression_right_paren_on_new_line = false +ij_javascript_place_assignment_sign_on_next_line = false +ij_javascript_prefer_as_type_cast = false +ij_javascript_prefer_explicit_types_function_expression_returns = false +ij_javascript_prefer_explicit_types_function_returns = false +ij_javascript_prefer_explicit_types_vars_fields = false +ij_javascript_prefer_parameters_wrap = false +ij_javascript_property_prefix = +ij_javascript_reformat_c_style_comments = false +ij_javascript_space_after_colon = true +ij_javascript_space_after_comma = true +ij_javascript_space_after_dots_in_rest_parameter = false +ij_javascript_space_after_generator_mult = true +ij_javascript_space_after_property_colon = true +ij_javascript_space_after_quest = true +ij_javascript_space_after_type_colon = true +ij_javascript_space_after_unary_not = false +ij_javascript_space_before_async_arrow_lparen = true +ij_javascript_space_before_catch_keyword = true +ij_javascript_space_before_catch_left_brace = true +ij_javascript_space_before_catch_parentheses = true +ij_javascript_space_before_class_lbrace = true +ij_javascript_space_before_class_left_brace = true +ij_javascript_space_before_colon = true +ij_javascript_space_before_comma = false +ij_javascript_space_before_do_left_brace = true +ij_javascript_space_before_else_keyword = true +ij_javascript_space_before_else_left_brace = true +ij_javascript_space_before_finally_keyword = true +ij_javascript_space_before_finally_left_brace = true +ij_javascript_space_before_for_left_brace = true +ij_javascript_space_before_for_parentheses = true +ij_javascript_space_before_for_semicolon = false +ij_javascript_space_before_function_left_parenth = true +ij_javascript_space_before_generator_mult = false +ij_javascript_space_before_if_left_brace = true +ij_javascript_space_before_if_parentheses = true +ij_javascript_space_before_method_call_parentheses = false +ij_javascript_space_before_method_left_brace = true +ij_javascript_space_before_method_parentheses = false +ij_javascript_space_before_property_colon = false +ij_javascript_space_before_quest = true +ij_javascript_space_before_switch_left_brace = true +ij_javascript_space_before_switch_parentheses = true +ij_javascript_space_before_try_left_brace = true +ij_javascript_space_before_type_colon = false +ij_javascript_space_before_unary_not = false +ij_javascript_space_before_while_keyword = true +ij_javascript_space_before_while_left_brace = true +ij_javascript_space_before_while_parentheses = true +ij_javascript_spaces_around_additive_operators = true +ij_javascript_spaces_around_arrow_function_operator = true +ij_javascript_spaces_around_assignment_operators = true +ij_javascript_spaces_around_bitwise_operators = true +ij_javascript_spaces_around_equality_operators = true +ij_javascript_spaces_around_logical_operators = true +ij_javascript_spaces_around_multiplicative_operators = true +ij_javascript_spaces_around_relational_operators = true +ij_javascript_spaces_around_shift_operators = true +ij_javascript_spaces_around_unary_operator = false +ij_javascript_spaces_within_array_initializer_brackets = false +ij_javascript_spaces_within_brackets = false +ij_javascript_spaces_within_catch_parentheses = false +ij_javascript_spaces_within_for_parentheses = false +ij_javascript_spaces_within_if_parentheses = false +ij_javascript_spaces_within_imports = false +ij_javascript_spaces_within_interpolation_expressions = false +ij_javascript_spaces_within_method_call_parentheses = false +ij_javascript_spaces_within_method_parentheses = false +ij_javascript_spaces_within_object_literal_braces = false +ij_javascript_spaces_within_object_type_braces = true +ij_javascript_spaces_within_parentheses = false +ij_javascript_spaces_within_switch_parentheses = false +ij_javascript_spaces_within_type_assertion = false +ij_javascript_spaces_within_union_types = true +ij_javascript_spaces_within_while_parentheses = false +ij_javascript_special_else_if_treatment = true +ij_javascript_ternary_operation_signs_on_next_line = false +ij_javascript_ternary_operation_wrap = off +ij_javascript_union_types_wrap = on_every_item +ij_javascript_use_chained_calls_group_indents = false +ij_javascript_use_double_quotes = true +ij_javascript_use_explicit_js_extension = auto +ij_javascript_use_import_type = auto +ij_javascript_use_path_mapping = always +ij_javascript_use_public_modifier = false +ij_javascript_use_semicolon_after_statement = true +ij_javascript_var_declaration_wrap = normal +ij_javascript_while_brace_force = never +ij_javascript_while_on_new_line = false +ij_javascript_wrap_comments = false + +[{*.ft,*.vm,*.vsl}] +ij_vtl_keep_indents_on_empty_lines = false + +[{*.gant,*.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_add_space = false +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_enable_groovydoc_formatting = true +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_ginq_general_clause_wrap_policy = 2 +ij_groovy_ginq_having_wrap_policy = 1 +ij_groovy_ginq_indent_having_clause = true +ij_groovy_ginq_indent_on_clause = true +ij_groovy_ginq_on_wrap_policy = 1 +ij_groovy_ginq_space_after_keyword = true +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_add_space_on_reformat = 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_packages_to_use_import_on_demand = java.awt.*,javax.swing.* +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_record_parentheses = false +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_chain_calls_after_dot = false +ij_groovy_wrap_long_lines = false + +[{*.graphqlconfig,*.graphqlrc,*.har,*.jsb2,*.jsb3,*.json,*.jsonc,*.png.mcmeta,*.postman_collection,*.postman_collection.json,*.postman_environment,*.postman_environment.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,.ws-context,jest.config}] +indent_size = 2 +ij_json_array_wrapping = split_into_lines +ij_json_keep_blank_lines_in_code = 0 +ij_json_keep_indents_on_empty_lines = false +ij_json_keep_line_breaks = true +ij_json_keep_trailing_comma = false +ij_json_object_wrapping = split_into_lines +ij_json_property_alignment = do_not_align +ij_json_space_after_colon = true +ij_json_space_after_comma = true +ij_json_space_before_colon = false +ij_json_space_before_comma = false +ij_json_spaces_within_braces = false +ij_json_spaces_within_brackets = false +ij_json_wrap_long_lines = false + +[{*.hcl,*.nomad}] +indent_size = 2 +ij_hcl_keep_blank_lines_in_code = 2 +ij_hcl_keep_indents_on_empty_lines = false +ij_hcl_keep_line_breaks = true +ij_hcl_space_after_comma = true +ij_hcl_space_before_comma = false +ij_hcl_spaces_around_assignment_operators = true +ij_hcl_spaces_within_braces = false +ij_hcl_spaces_within_brackets = false +ij_hcl_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_add_space = false +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 + +[{*.http,*.rest}] +ij_continuation_indent_size = 4 +ij_http-request_call_parameters_wrap = normal +ij_http-request_method_parameters_wrap = split_into_lines +ij_http-request_space_before_comma = true +ij_http-request_spaces_around_assignment_operators = true + +[{*.jsf,*.jsp,*.jspf,*.tag,*.tagf,*.xjsp}] +ij_jsp_jsp_prefer_comma_separated_import_list = false +ij_jsp_keep_indents_on_empty_lines = false + +[{*.jspx,*.tagx}] +ij_jspx_keep_indents_on_empty_lines = false + +[{*.kt,*.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_collection_literal_expression = false +ij_kotlin_allow_trailing_comma_context_receiver_list = true +ij_kotlin_allow_trailing_comma_destructuring_declaration = true +ij_kotlin_allow_trailing_comma_function_literal = true +ij_kotlin_allow_trailing_comma_indices = false +ij_kotlin_allow_trailing_comma_on_call_site = false +ij_kotlin_allow_trailing_comma_type_argument_list = false +ij_kotlin_allow_trailing_comma_type_parameter_list = true +ij_kotlin_allow_trailing_comma_value_argument_list = false +ij_kotlin_allow_trailing_comma_value_parameter_list = true +ij_kotlin_allow_trailing_comma_when_entry = true +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_add_space = false +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_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_function_context_parameters_wrap = split_into_lines +ij_kotlin_if_rparen_on_new_line = true +ij_kotlin_import_nested_classes = false +ij_kotlin_imports_layout = *,java.**,javax.**,kotlin.**,^ +ij_kotlin_indent_before_arrow_on_new_line = true +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_break_after_multiline_when_entry = true +ij_kotlin_line_comment_add_space = false +ij_kotlin_line_comment_add_space_on_reformat = 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_property_context_parameters_wrap = split_into_lines +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_elvis = 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 + +[{*.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_format_tables = true +ij_markdown_insert_quote_arrows_on_wrap = true +ij_markdown_keep_indents_on_empty_lines = false +ij_markdown_keep_line_breaks_inside_text_blocks = true +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 +ij_markdown_wrap_text_if_long = true +ij_markdown_wrap_text_inside_blockquotes = true + +[{*.pb,*.textproto,*.txtpb}] +indent_size = 2 +tab_width = 2 +ij_continuation_indent_size = 4 +ij_prototext_keep_blank_lines_in_code = 2 +ij_prototext_keep_indents_on_empty_lines = false +ij_prototext_keep_line_breaks = true +ij_prototext_space_after_colon = true +ij_prototext_space_after_comma = true +ij_prototext_space_before_colon = false +ij_prototext_space_before_comma = false +ij_prototext_spaces_within_braces = true +ij_prototext_spaces_within_brackets = false + +[{*.properties,spring.handlers,spring.schemas}] +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 + +[{*.qute.htm,*.qute.html,*.qute.json,*.qute.txt,*.qute.yaml,*.qute.yml}] +ij_qute_keep_indents_on_empty_lines = false + +[{*.tf,*.tfvars}] +indent_size = 2 +ij_hcl-terraform_array_wrapping = normal +ij_hcl-terraform_import_providers_automatically = true +ij_hcl-terraform_keep_blank_lines_in_code = 2 +ij_hcl-terraform_keep_indents_on_empty_lines = false +ij_hcl-terraform_keep_line_breaks = true +ij_hcl-terraform_line_commenter_character = pound_sign_(#) +ij_hcl-terraform_object_wrapping = normal +ij_hcl-terraform_property_alignment = on_equals +ij_hcl-terraform_run_tf_fmt_on_reformat = true +ij_hcl-terraform_space_after_comma = true +ij_hcl-terraform_space_before_comma = false +ij_hcl-terraform_spaces_around_assignment_operators = true +ij_hcl-terraform_spaces_within_braces = false +ij_hcl-terraform_spaces_within_brackets = false +ij_hcl-terraform_wrap_long_lines = false + +[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,pdm.lock,poetry.lock,uv.lock}] +ij_toml_keep_indents_on_empty_lines = false + +[{*.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_line_comment_add_space = false +ij_yaml_line_comment_add_space_on_reformat = false +ij_yaml_line_comment_at_first_column = 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 diff --git a/.github/workflows/test-gradle.yml b/.github/workflows/test-gradle.yml index 5cd7e1e..e920e9e 100644 --- a/.github/workflows/test-gradle.yml +++ b/.github/workflows/test-gradle.yml @@ -31,4 +31,4 @@ jobs: name: AntiVPN-Universal path: build/libs/AntiVPN-*-universal.jar - name: Test - run: gradle test --no-daemon + run: gradle test --no-daemon --parallel diff --git a/Bukkit/Loader/build.gradle b/Bukkit/Loader/build.gradle index 87ea1a6..590a9e1 100644 --- a/Bukkit/Loader/build.gradle +++ b/Bukkit/Loader/build.gradle @@ -30,4 +30,8 @@ tasks.named('shadowJar') { dependsOn(':Bukkit:Plugin:shadowJar') } +tasks.named('compileJava') { + dependsOn(':Bukkit:Plugin:shadowJar') +} + tasks.build.dependsOn shadowJar diff --git a/Bukkit/Loader/src/main/java/dev/brighten/antivpn/bukkit/loader/BukkitLoaderPlugin.java b/Bukkit/Loader/src/main/java/dev/brighten/antivpn/bukkit/loader/BukkitLoaderPlugin.java index 81048eb..52008dc 100644 --- a/Bukkit/Loader/src/main/java/dev/brighten/antivpn/bukkit/loader/BukkitLoaderPlugin.java +++ b/Bukkit/Loader/src/main/java/dev/brighten/antivpn/bukkit/loader/BukkitLoaderPlugin.java @@ -22,31 +22,30 @@ import org.bukkit.plugin.java.JavaPlugin; public class BukkitLoaderPlugin extends JavaPlugin { - private static final String JAR_NAME = "antivpn-bukkit.jarinjar"; - private static final String SOURCE_NAME = "antivpn-source.jarinjar"; - private static final String BOOTSTRAP_CLASS = "dev.brighten.antivpn.bukkit.BukkitPlugin"; + private static final String JAR_NAME = "antivpn-bukkit.jarinjar"; + private static final String SOURCE_NAME = "antivpn-source.jarinjar"; + private static final String BOOTSTRAP_CLASS = "dev.brighten.antivpn.bukkit.BukkitPlugin"; - private final LoaderBootstrap plugin; + private final LoaderBootstrap plugin; - public BukkitLoaderPlugin() { - JarInJarClassLoader loader = new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME); - this.plugin = loader.instantiatePlugin(BOOTSTRAP_CLASS, JavaPlugin.class, this); - } + public BukkitLoaderPlugin() { + JarInJarClassLoader loader = + new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME); + this.plugin = loader.instantiatePlugin(BOOTSTRAP_CLASS, JavaPlugin.class, this); + } - @Override - public void onLoad() { - this.plugin.onLoad(getDataFolder()); - } - - @Override - public void onEnable() { - this.plugin.onEnable(); - } - - @Override - public void onDisable() { - this.plugin.onDisable(); - } + @Override + public void onLoad() { + this.plugin.onLoad(getDataFolder()); + } + @Override + public void onEnable() { + this.plugin.onEnable(); + } + @Override + public void onDisable() { + this.plugin.onDisable(); + } } diff --git a/Bukkit/Plugin/build.gradle b/Bukkit/Plugin/build.gradle index 27dc8e8..d9ad310 100644 --- a/Bukkit/Plugin/build.gradle +++ b/Bukkit/Plugin/build.gradle @@ -12,6 +12,7 @@ dependencies { testImplementation 'org.mockito:mockito-core:5.11.0' testImplementation 'org.mockito:mockito-subclass:5.11.0' testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0' + testImplementation testFixtures(project(':Common:Source')) } shadowJar { diff --git a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitCommandExecutor.java b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitCommandExecutor.java index a1a8001..be806c2 100644 --- a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitCommandExecutor.java +++ b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitCommandExecutor.java @@ -19,38 +19,37 @@ package dev.brighten.antivpn.bukkit; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.command.CommandExecutor; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; -import java.util.Optional; - @RequiredArgsConstructor public class BukkitCommandExecutor implements CommandExecutor { - private final CommandSender sender; + private final CommandSender sender; - @Override - public void sendMessage(String message, Object... objects) { - sender.sendMessage(ChatColor.translateAlternateColorCodes('&', - String.format(message, objects))); - } + @Override + public void sendMessage(String message, Object... objects) { + sender.sendMessage( + ChatColor.translateAlternateColorCodes('&', String.format(message, objects))); + } - @Override - public boolean hasPermission(String permission) { - return sender.hasPermission(permission); - } + @Override + public boolean hasPermission(String permission) { + return sender.hasPermission(permission); + } - @Override - public Optional getPlayer() { - if(!isPlayer()) return Optional.empty(); + @Override + public Optional getPlayer() { + if (!isPlayer()) return Optional.empty(); - return AntiVPN.getInstance().getPlayerExecutor().getPlayer(((Player)sender).getUniqueId()); - } + return AntiVPN.getInstance().getPlayerExecutor().getPlayer(((Player) sender).getUniqueId()); + } - @Override - public boolean isPlayer() { - return sender instanceof Player; - } + @Override + public boolean isPlayer() { + return sender instanceof Player; + } } diff --git a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitListener.java b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitListener.java index f0f1ab7..cdcf33a 100644 --- a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitListener.java +++ b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitListener.java @@ -22,6 +22,7 @@ import dev.brighten.antivpn.api.CheckResult; import dev.brighten.antivpn.api.OfflinePlayer; import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.utils.StringUtil; +import java.util.logging.Level; import net.md_5.bungee.api.ChatColor; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; @@ -33,89 +34,90 @@ import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.scheduler.BukkitRunnable; -import java.util.logging.Level; - public class BukkitListener extends VPNExecutor implements Listener { - @Override - public void registerListeners() { - Bukkit.getPluginManager() - .registerEvents(this, BukkitPlugin.pluginInstance.getPlugin()); + @Override + public void registerListeners() { + Bukkit.getPluginManager().registerEvents(this, BukkitPlugin.pluginInstance.getPlugin()); + } + + @Override + public void log(Level level, String log, Object... objects) { + Bukkit.getLogger().log(level, String.format(log, objects)); + } + + @Override + public void log(String log, Object... objects) { + log(Level.INFO, String.format(log, objects)); + } + + @Override + public void logException(String message, Throwable ex) { + Bukkit.getLogger().log(Level.SEVERE, message, ex); + } + + @Override + public void runCommand(String command) { + new BukkitRunnable() { + public void run() { + Bukkit.getServer() + .dispatchCommand( + Bukkit.getConsoleSender(), ChatColor.translateAlternateColorCodes('&', command)); + } + }.runTask(BukkitPlugin.pluginInstance.getPlugin()); + } + + @Override + public void disablePlugin() { + HandlerList.unregisterAll(this); + Bukkit.getPluginManager().disablePlugin(BukkitPlugin.pluginInstance.getPlugin()); + } + + @EventHandler(priority = EventPriority.HIGH) + public void onLogin(final PlayerLoginEvent event) { + APIPlayer player = + AntiVPN.getInstance() + .getPlayerExecutor() + .getPlayer(event.getPlayer().getUniqueId()) + .orElse( + new OfflinePlayer( + event.getPlayer().getUniqueId(), + event.getPlayer().getName(), + event.getAddress())); + + CheckResult result = player.checkPlayer(); + + if (!result.resultType().isShouldBlock()) return; + + if (!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { + return; } - @Override - public void log(Level level, String log, Object... objects) { - Bukkit.getLogger().log(level, String.format(log, objects)); - } - - @Override - public void log(String log, Object... objects) { - log(Level.INFO, String.format(log, objects)); - } - - @Override - public void logException(String message, Throwable ex) { - Bukkit.getLogger().log(Level.SEVERE, message, ex); - } - - @Override - public void runCommand(String command) { - new BukkitRunnable() { - public void run() { - Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), - ChatColor.translateAlternateColorCodes('&', command)); - } - }.runTask(BukkitPlugin.pluginInstance.getPlugin()); - } - - @Override - public void disablePlugin() { - HandlerList.unregisterAll(this); - Bukkit.getPluginManager().disablePlugin(BukkitPlugin.pluginInstance.getPlugin()); - } - - @EventHandler(priority = EventPriority.HIGH) - public void onLogin(final PlayerLoginEvent event) { - APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId()) - .orElse(new OfflinePlayer( - event.getPlayer().getUniqueId(), - event.getPlayer().getName(), - event.getAddress() - )); - - CheckResult result = player.checkPlayer(); - - if(!result.resultType().isShouldBlock()) return; - - if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { - return; - } - - event.setResult(PlayerLoginEvent.Result.KICK_BANNED); - event.setKickMessage(switch (result.resultType()) { - case DENIED_COUNTRY -> StringUtil.varReplace( - AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(), - player, - result.response() - ); - case DENIED_PROXY -> - StringUtil.varReplace( - AntiVPN.getInstance().getVpnConfig().getKickMessage(), - player, - result.response() - ); - default -> "You were kicked by KauriVPN for an unknown reason!"; + event.setResult(PlayerLoginEvent.Result.KICK_BANNED); + event.setKickMessage( + switch (result.resultType()) { + case DENIED_COUNTRY -> + StringUtil.varReplace( + AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(), + player, + result.response()); + case DENIED_PROXY -> + StringUtil.varReplace( + AntiVPN.getInstance().getVpnConfig().getKickMessage(), player, result.response()); + default -> "You were kicked by KauriVPN for an unknown reason!"; }); - } + } - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onJoin(final PlayerJoinEvent event) { - AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId()) - .ifPresent(APIPlayer::checkAlertsState); - } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onJoin(final PlayerJoinEvent event) { + AntiVPN.getInstance() + .getPlayerExecutor() + .getPlayer(event.getPlayer().getUniqueId()) + .ifPresent(APIPlayer::checkAlertsState); + } - @EventHandler - public void onQuit(PlayerQuitEvent event) { - AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(event.getPlayer().getUniqueId()); - } + @EventHandler + public void onQuit(PlayerQuitEvent event) { + AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(event.getPlayer().getUniqueId()); + } } diff --git a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlayer.java b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlayer.java index e1c2af5..14b2d98 100644 --- a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlayer.java +++ b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlayer.java @@ -23,29 +23,33 @@ import org.bukkit.scheduler.BukkitRunnable; public class BukkitPlayer extends APIPlayer { - private final Player player; - public BukkitPlayer(Player player) { - super(player.getUniqueId(), player.getName(), player.getAddress() != null ? player.getAddress().getAddress() : null); + private final Player player; - this.player = player; - } + public BukkitPlayer(Player player) { + super( + player.getUniqueId(), + player.getName(), + player.getAddress() != null ? player.getAddress().getAddress() : null); - @Override - public void sendMessage(String message) { - player.sendMessage(ChatColor.translateAlternateColorCodes('&', message)); - } + this.player = player; + } - @Override - public void kickPlayer(String reason) { - new BukkitRunnable() { - public void run() { - player.kickPlayer(ChatColor.translateAlternateColorCodes('&', reason)); - } - }.runTask(BukkitPlugin.pluginInstance.getPlugin()); - } + @Override + public void sendMessage(String message) { + player.sendMessage(ChatColor.translateAlternateColorCodes('&', message)); + } - @Override - public boolean hasPermission(String permission) { - return player.hasPermission(permission); - } + @Override + public void kickPlayer(String reason) { + new BukkitRunnable() { + public void run() { + player.kickPlayer(ChatColor.translateAlternateColorCodes('&', reason)); + } + }.runTask(BukkitPlugin.pluginInstance.getPlugin()); + } + + @Override + public boolean hasPermission(String permission) { + return player.hasPermission(permission); + } } diff --git a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlayerExecutor.java b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlayerExecutor.java index 3a6ee4c..a67398c 100644 --- a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlayerExecutor.java +++ b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlayerExecutor.java @@ -18,49 +18,48 @@ package dev.brighten.antivpn.bukkit; import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.PlayerExecutor; +import java.util.*; +import java.util.stream.Collectors; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import java.util.*; -import java.util.stream.Collectors; - public class BukkitPlayerExecutor implements PlayerExecutor { - private final Map cachedPlayers = new HashMap<>(); + private final Map cachedPlayers = new HashMap<>(); - @Override - public Optional getPlayer(String name) { - final Player player = Bukkit.getPlayer(name); + @Override + public Optional getPlayer(String name) { + final Player player = Bukkit.getPlayer(name); - if(player == null) { - return Optional.empty(); - } - - return Optional.of(cachedPlayers.computeIfAbsent(player.getUniqueId(), k -> new BukkitPlayer(player))); + if (player == null) { + return Optional.empty(); } - @Override - public Optional getPlayer(UUID uuid) { - final Player player = Bukkit.getPlayer(uuid); + return Optional.of( + cachedPlayers.computeIfAbsent(player.getUniqueId(), k -> new BukkitPlayer(player))); + } - if(player == null) { - return Optional.empty(); - } + @Override + public Optional getPlayer(UUID uuid) { + final Player player = Bukkit.getPlayer(uuid); - return Optional.of(cachedPlayers.computeIfAbsent(player.getUniqueId(), k -> new BukkitPlayer(player))); + if (player == null) { + return Optional.empty(); } - @Override - public void unloadPlayer(UUID uuid) { - cachedPlayers.remove(uuid); - } + return Optional.of( + cachedPlayers.computeIfAbsent(player.getUniqueId(), k -> new BukkitPlayer(player))); + } + @Override + public void unloadPlayer(UUID uuid) { + cachedPlayers.remove(uuid); + } - @Override - public List getOnlinePlayers() { - return Bukkit.getOnlinePlayers().stream() - .map(pl -> cachedPlayers.computeIfAbsent(pl.getUniqueId(), k -> new BukkitPlayer(pl))) - .collect(Collectors.toList()); - } - + @Override + public List getOnlinePlayers() { + return Bukkit.getOnlinePlayers().stream() + .map(pl -> cachedPlayers.computeIfAbsent(pl.getUniqueId(), k -> new BukkitPlayer(pl))) + .collect(Collectors.toList()); + } } diff --git a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlugin.java b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlugin.java index 1cab6a2..3d10afd 100644 --- a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlugin.java +++ b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/BukkitPlugin.java @@ -24,6 +24,11 @@ import dev.brighten.antivpn.database.local.H2VPN; import dev.brighten.antivpn.database.mongo.MongoVPN; import dev.brighten.antivpn.database.sql.MySqlVPN; import dev.brighten.antivpn.loader.LoaderBootstrap; +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; import lombok.Getter; import org.bstats.bukkit.Metrics; import org.bstats.charts.SimplePie; @@ -34,129 +39,124 @@ import org.bukkit.plugin.SimplePluginManager; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitRunnable; -import java.io.File; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - public class BukkitPlugin implements LoaderBootstrap { - public static BukkitPlugin pluginInstance; - private SimpleCommandMap commandMap; - @Getter - private File dataFolder; - private final List registeredCommands = new ArrayList<>(); - @Getter - private final JavaPlugin plugin; + public static BukkitPlugin pluginInstance; + private SimpleCommandMap commandMap; + @Getter private File dataFolder; + private final List registeredCommands = new ArrayList<>(); + @Getter private final JavaPlugin plugin; - public BukkitPlugin(JavaPlugin plugin) { - this.plugin = plugin; + public BukkitPlugin(JavaPlugin plugin) { + this.plugin = plugin; + } + + @Getter private PlayerCommandRunner playerCommandRunner; + + @Override + public void onLoad(File dataFolder) { + this.dataFolder = dataFolder; + } + + public void onEnable() { + pluginInstance = this; + + Bukkit.getLogger().info("Starting AntiVPN services..."); + AntiVPN.start(new BukkitListener(), new BukkitPlayerExecutor(), getDataFolder()); + + playerCommandRunner = new PlayerCommandRunner(); + playerCommandRunner.start(); + + // Loading our bStats metrics to be pushed to https://bstats.org + if (AntiVPN.getInstance().getVpnConfig().metrics()) { + Bukkit.getLogger().info("Starting bStats metrics..."); + Metrics metrics = new Metrics(plugin, 12615); + metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType)); + new BukkitRunnable() { + public void run() { + AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0; + } + }.runTaskTimerAsynchronously(plugin, 12000, 12000); } - @Getter - private PlayerCommandRunner playerCommandRunner; - - @Override - public void onLoad(File dataFolder) { - this.dataFolder = dataFolder; + Bukkit.getLogger().info("Setting up and registering commands..."); + // We need access to the commandMap to register our commands without using the "proper" method + if (Bukkit.getServer().getPluginManager() instanceof SimplePluginManager manager) { + try { + Field field = SimplePluginManager.class.getDeclaredField("commandMap"); + field.setAccessible(true); + commandMap = (SimpleCommandMap) field.get(manager); + } catch (IllegalArgumentException + | SecurityException + | NoSuchFieldException + | IllegalAccessException e) { + AntiVPN.getInstance().getExecutor().logException(e); + } } - public void onEnable() { - pluginInstance = this; + // Registering commands + for (Command command : AntiVPN.getInstance().getCommands()) { + // Wraps our general command API to Bukkit specific calls + BukkitCommand newCommand = new BukkitCommand(command); - Bukkit.getLogger().info("Starting AntiVPN services..."); - AntiVPN.start(new BukkitListener(), new BukkitPlayerExecutor(), getDataFolder()); + // Adding to our own list for later referencing + registeredCommands.add(newCommand); - playerCommandRunner = new PlayerCommandRunner(); - playerCommandRunner.start(); - - // Loading our bStats metrics to be pushed to https://bstats.org - if(AntiVPN.getInstance().getVpnConfig().metrics()) { - Bukkit.getLogger().info("Starting bStats metrics..."); - Metrics metrics = new Metrics(plugin, 12615); - metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType)); - new BukkitRunnable() { - public void run() { - AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0; - } - }.runTaskTimerAsynchronously(plugin, 12000, 12000); - } - - Bukkit.getLogger().info("Setting up and registering commands..."); - // We need access to the commandMap to register our commands without using the "proper" method - if (Bukkit.getServer().getPluginManager() instanceof SimplePluginManager manager) { - try { - Field field = SimplePluginManager.class.getDeclaredField("commandMap"); - field.setAccessible(true); - commandMap = (SimpleCommandMap) field.get(manager); - } catch (IllegalArgumentException | SecurityException | NoSuchFieldException | IllegalAccessException e) { - AntiVPN.getInstance().getExecutor().logException(e); - } - } - - // Registering commands - for (Command command : AntiVPN.getInstance().getCommands()) { - // Wraps our general command API to Bukkit specific calls - BukkitCommand newCommand = new BukkitCommand(command); - - // Adding to our own list for later referencing - registeredCommands.add(newCommand); - - // This tells Bukkit to register our command for use. - commandMap.register(plugin.getName(), newCommand); - } - - //TODO Finish system before implementing on startup - /*Bukkit.getLogger().info("Getting strings..."); - AntiVPN.getInstance().getMessageHandler().initStrings(vpnString -> new ConfigDefault<> - (vpnString.getDefaultMessage(), "messages." + vpnString.getKey(), BukkitPlugin.pluginInstance) - .get()); - AntiVPN.getInstance().getMessageHandler().reloadStrings();*/ - - plugin.reloadConfig(); + // This tells Bukkit to register our command for use. + commandMap.register(plugin.getName(), newCommand); } - @Override - @SuppressWarnings("unchecked") - public void onDisable() { - Bukkit.getLogger().info("Stopping plugin services..."); - AntiVPN.getInstance().stop(); - playerCommandRunner.stop(); + // TODO Finish system before implementing on startup + /*Bukkit.getLogger().info("Getting strings..."); + AntiVPN.getInstance().getMessageHandler().initStrings(vpnString -> new ConfigDefault<> + (vpnString.getDefaultMessage(), "messages." + vpnString.getKey(), BukkitPlugin.pluginInstance) + .get()); + AntiVPN.getInstance().getMessageHandler().reloadStrings();*/ - Bukkit.getLogger().info("Unregistering commands..."); - try { - Field field = SimpleCommandMap.class.getDeclaredField("knownCommands"); - field.setAccessible(true); + plugin.reloadConfig(); + } - if(field.get(commandMap) instanceof Map knownCommands) { - Map casted = (Map) knownCommands; - casted.values().removeAll(registeredCommands); - registeredCommands.clear(); - } + @Override + @SuppressWarnings("unchecked") + public void onDisable() { + Bukkit.getLogger().info("Stopping plugin services..."); + AntiVPN.getInstance().stop(); + playerCommandRunner.stop(); - } catch (IllegalAccessException | NoSuchFieldException e) { - AntiVPN.getInstance().getExecutor().logException(e); - } + Bukkit.getLogger().info("Unregistering commands..."); + try { + Field field = SimpleCommandMap.class.getDeclaredField("knownCommands"); + field.setAccessible(true); - Bukkit.getLogger().info("Unregistering listeners..."); - HandlerList.unregisterAll(plugin); + if (field.get(commandMap) instanceof Map knownCommands) { + Map casted = + (Map) knownCommands; + casted.values().removeAll(registeredCommands); + registeredCommands.clear(); + } - Bukkit.getLogger().info("Cancelling any running tasks..."); - Bukkit.getScheduler().cancelTasks(plugin); + } catch (IllegalAccessException | NoSuchFieldException e) { + AntiVPN.getInstance().getExecutor().logException(e); } - private String getDatabaseType() { - VPNDatabase database = AntiVPN.getInstance().getDatabase(); + Bukkit.getLogger().info("Unregistering listeners..."); + HandlerList.unregisterAll(plugin); - if(database instanceof MySqlVPN) { - return "MySQL"; - } else if(database instanceof H2VPN) { - return "H2"; - } else if(database instanceof MongoVPN) { - return "MongoDB"; - } else { - return "No-Database"; - } + Bukkit.getLogger().info("Cancelling any running tasks..."); + Bukkit.getScheduler().cancelTasks(plugin); + } + + private String getDatabaseType() { + VPNDatabase database = AntiVPN.getInstance().getDatabase(); + + if (database instanceof MySqlVPN) { + return "MySQL"; + } else if (database instanceof H2VPN) { + return "H2"; + } else if (database instanceof MongoVPN) { + return "MongoDB"; + } else { + return "No-Database"; } + } } diff --git a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/PlayerCommandRunner.java b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/PlayerCommandRunner.java index 1b240e7..a84aaee 100644 --- a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/PlayerCommandRunner.java +++ b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/PlayerCommandRunner.java @@ -17,61 +17,64 @@ package dev.brighten.antivpn.bukkit; import dev.brighten.antivpn.utils.MiscUtils; -import lombok.Data; -import org.bukkit.Bukkit; -import org.bukkit.scheduler.BukkitRunnable; - import java.util.Queue; import java.util.UUID; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import lombok.Data; +import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitRunnable; public class PlayerCommandRunner { - private final ScheduledExecutorService executorService; - private final Queue playerActions = new ArrayBlockingQueue<>(10000); + private final ScheduledExecutorService executorService; + private final Queue playerActions = new ArrayBlockingQueue<>(10000); - public PlayerCommandRunner() { - executorService = Executors.newSingleThreadScheduledExecutor( - MiscUtils.createThreadFactory("AntiVPN:PlayerCommandRunner") - ); - } + public PlayerCommandRunner() { + executorService = + Executors.newSingleThreadScheduledExecutor( + MiscUtils.createThreadFactory("AntiVPN:PlayerCommandRunner")); + } - void start() { - executorService.scheduleAtFixedRate(() -> { - long currentTime = System.currentTimeMillis(); - while(!playerActions.isEmpty()) { - PlayerAction action = playerActions.peek(); + void start() { + executorService.scheduleAtFixedRate( + () -> { + long currentTime = System.currentTimeMillis(); + while (!playerActions.isEmpty()) { + PlayerAction action = playerActions.peek(); - if(action == null) continue; + if (action == null) continue; - if(currentTime - action.start > 2000L || Bukkit.getPlayer(action.getUuid()) != null) { - new BukkitRunnable() { - public void run() { - action.getAction().run(); - } - }.runTask(BukkitPlugin.pluginInstance.getPlugin()); - - playerActions.poll(); + if (currentTime - action.start > 2000L || Bukkit.getPlayer(action.getUuid()) != null) { + new BukkitRunnable() { + public void run() { + action.getAction().run(); } + }.runTask(BukkitPlugin.pluginInstance.getPlugin()); + + playerActions.poll(); } - }, 1000, 100, TimeUnit.MILLISECONDS); - } + } + }, + 1000, + 100, + TimeUnit.MILLISECONDS); + } - void stop() { - executorService.shutdown(); - playerActions.clear(); - } + void stop() { + executorService.shutdown(); + playerActions.clear(); + } - void addAction(UUID uuid, Runnable action) { - playerActions.add(new PlayerAction(uuid, System.currentTimeMillis(), action)); - } + void addAction(UUID uuid, Runnable action) { + playerActions.add(new PlayerAction(uuid, System.currentTimeMillis(), action)); + } - @Data - static class PlayerAction { - private final UUID uuid; - private final long start; - private final Runnable action; - } + @Data + static class PlayerAction { + private final UUID uuid; + private final long start; + private final Runnable action; + } } diff --git a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/command/BukkitCommand.java b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/command/BukkitCommand.java index 132de49..a670242 100644 --- a/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/command/BukkitCommand.java +++ b/Bukkit/Plugin/src/main/java/dev/brighten/antivpn/bukkit/command/BukkitCommand.java @@ -19,75 +19,90 @@ package dev.brighten.antivpn.bukkit.command; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.bukkit.BukkitCommandExecutor; import dev.brighten.antivpn.command.Command; +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; import lombok.val; import net.md_5.bungee.api.ChatColor; import org.bukkit.command.CommandSender; -import java.util.Arrays; -import java.util.List; -import java.util.stream.IntStream; - public class BukkitCommand extends org.bukkit.command.Command { - private final Command command; - public BukkitCommand(Command command) { - super(command.name(), command.description(), command.usage(), Arrays.asList(command.aliases())); + private final Command command; - this.command = command; - } + public BukkitCommand(Command command) { + super(command.name(), command.description(), command.usage(), Arrays.asList(command.aliases())); - @Override - public List tabComplete(CommandSender sender, String alias, String[] args) - throws IllegalArgumentException { - val children = command.children(); + this.command = command; + } - if(children.length > 0 && args.length > 0) { - for (Command child : children) { - if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases()) - .anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) { - return child.tabComplete(new BukkitCommandExecutor(sender), alias, IntStream - .range(0, args.length - 1) - .mapToObj(i -> args[i + 1]).toArray(String[]::new)); - } - } + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) + throws IllegalArgumentException { + val children = command.children(); + + if (children.length > 0 && args.length > 0) { + for (Command child : children) { + if (child.name().equalsIgnoreCase(args[0]) + || Arrays.stream(child.aliases()) + .anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) { + return child.tabComplete( + new BukkitCommandExecutor(sender), + alias, + IntStream.range(0, args.length - 1) + .mapToObj(i -> args[i + 1]) + .toArray(String[]::new)); } - return command.tabComplete(new BukkitCommandExecutor(sender), alias, args); + } + } + return command.tabComplete(new BukkitCommandExecutor(sender), alias, args); + } + + @Override + public boolean execute(CommandSender sender, String s, String[] args) { + if (!sender.hasPermission("antivpn.command.*") && !sender.hasPermission(command.permission())) { + sender.sendMessage( + ChatColor.translateAlternateColorCodes( + '&', + AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage())); + return true; } - @Override - public boolean execute(CommandSender sender, String s, String[] args) { - if(!sender.hasPermission("antivpn.command.*") - && !sender.hasPermission(command.permission())) { - sender.sendMessage(ChatColor.translateAlternateColorCodes('&', - AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage())); + val children = command.children(); + + if (children.length > 0 && args.length > 0) { + for (Command child : children) { + if (child.name().equalsIgnoreCase(args[0]) + || Arrays.stream(child.aliases()).anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) { + if (!sender.hasPermission("antivpn.command.*") + && !sender.hasPermission(child.permission())) { + sender.sendMessage( + ChatColor.translateAlternateColorCodes( + '&', + AntiVPN.getInstance() + .getMessageHandler() + .getString("no-permission") + .getMessage())); return true; + } + + sender.sendMessage( + ChatColor.translateAlternateColorCodes( + '&', + child.execute( + new BukkitCommandExecutor(sender), + IntStream.range(0, args.length - 1) + .mapToObj(i -> args[i + 1]) + .toArray(String[]::new)))); + return true; } - - val children = command.children(); - - if(children.length > 0 && args.length > 0) { - for (Command child : children) { - if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases()) - .anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) { - if(!sender.hasPermission("antivpn.command.*") - && !sender.hasPermission(child.permission())) { - sender.sendMessage(ChatColor.translateAlternateColorCodes('&', - AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage())); - return true; - } - - sender.sendMessage(ChatColor.translateAlternateColorCodes('&', - child.execute(new BukkitCommandExecutor(sender), IntStream - .range(0, args.length - 1) - .mapToObj(i -> args[i + 1]).toArray(String[]::new)))); - return true; - } - } - } - - sender.sendMessage(ChatColor.translateAlternateColorCodes('&', - command.execute(new BukkitCommandExecutor(sender), args))); - - return true; + } } + + sender.sendMessage( + ChatColor.translateAlternateColorCodes( + '&', command.execute(new BukkitCommandExecutor(sender), args))); + + return true; + } } diff --git a/Bukkit/Plugin/src/test/java/dev/brighten/antivpn/bukkit/BukkitListenerTest.java b/Bukkit/Plugin/src/test/java/dev/brighten/antivpn/bukkit/BukkitListenerTest.java index ae2c09e..4bc2d18 100644 --- a/Bukkit/Plugin/src/test/java/dev/brighten/antivpn/bukkit/BukkitListenerTest.java +++ b/Bukkit/Plugin/src/test/java/dev/brighten/antivpn/bukkit/BukkitListenerTest.java @@ -1,24 +1,20 @@ package dev.brighten.antivpn.bukkit; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.*; + import be.seeseemelk.mockbukkit.MockBukkit; import be.seeseemelk.mockbukkit.ServerMock; import be.seeseemelk.mockbukkit.entity.PlayerMock; import dev.brighten.antivpn.AntiVPN; -import dev.brighten.antivpn.api.PlayerExecutor; -import dev.brighten.antivpn.api.VPNConfig; -import dev.brighten.antivpn.api.VPNExecutor; +import dev.brighten.antivpn.StandardTest; +import dev.brighten.antivpn.api.*; import dev.brighten.antivpn.message.MessageHandler; import dev.brighten.antivpn.message.VpnString; import dev.brighten.antivpn.web.objects.VPNResponse; -import org.bukkit.event.player.PlayerLoginEvent; -import org.bukkit.command.CommandSender; -import org.bukkit.plugin.PluginDescriptionFile; -import org.bukkit.plugin.java.JavaPlugin; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.lang.reflect.Field; import java.net.InetAddress; import java.util.Optional; @@ -29,172 +25,179 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import org.bukkit.command.CommandSender; +import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.*; +public class BukkitListenerTest extends StandardTest { -public class BukkitListenerTest { + private ServerMock server; + private BukkitListener listener; + private VPNExecutor vpnExecutor; - private ServerMock server; - private BukkitListener listener; - private VPNExecutor vpnExecutor; + @BeforeEach + public void setUp() throws Exception { + server = MockBukkit.mock(new RecordingServerMock()); + JavaPlugin plugin = + MockBukkit.loadWith( + TestPlugin.class, + new PluginDescriptionFile("AntiVPNTest", "1.0.0", TestPlugin.class.getName())); + BukkitPlugin.pluginInstance = new BukkitPlugin(plugin); - @BeforeEach - public void setUp() throws Exception { - server = MockBukkit.mock(new RecordingServerMock()); - JavaPlugin plugin = MockBukkit.loadWith( - TestPlugin.class, - new PluginDescriptionFile("AntiVPNTest", "1.0.0", TestPlugin.class.getName()) - ); - BukkitPlugin.pluginInstance = new BukkitPlugin(plugin); + AntiVPN antiVPN = mock(AntiVPN.class); + VPNConfig config = mock(VPNConfig.class); + PlayerExecutor playerExecutor = mock(PlayerExecutor.class); + vpnExecutor = mock(VPNExecutor.class); + MessageHandler messageHandler = mock(MessageHandler.class); - AntiVPN antiVPN = mock(AntiVPN.class); - VPNConfig config = mock(VPNConfig.class); - PlayerExecutor playerExecutor = mock(PlayerExecutor.class); - vpnExecutor = mock(VPNExecutor.class); - MessageHandler messageHandler = mock(MessageHandler.class); - - when(antiVPN.getVpnConfig()).thenReturn(config); - when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor); - when(antiVPN.getExecutor()).thenReturn(vpnExecutor); - when(antiVPN.getMessageHandler()).thenReturn(messageHandler); - - when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty()); - when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList()); - when(config.getCountryList()).thenReturn(java.util.Collections.emptyList()); - when(config.isKickPlayers()).thenReturn(true); - when(config.getKickMessage()).thenReturn("Blocked!"); - - VpnString mockVpnString = mock(VpnString.class); - when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!"); - when(messageHandler.getString(anyString())).thenReturn(mockVpnString); - - when(vpnExecutor.checkIp(anyString())).thenReturn(CompletableFuture.completedFuture( - VPNResponse.builder().success(true).proxy(false).ip("127.0.0.1") - .method("N/A").countryName("N/A").city("N/A").build() - )); - - // Use reflection to set the private static INSTANCE field - Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); - instanceField.setAccessible(true); - instanceField.set(null, antiVPN); - - listener = new BukkitListener(); + when(antiVPN.getVpnConfig()).thenReturn(config); + when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor); + when(antiVPN.getExecutor()).thenReturn(vpnExecutor); + when(antiVPN.getMessageHandler()).thenReturn(messageHandler); + + when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty()); + when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList()); + when(config.getCountryList()).thenReturn(java.util.Collections.emptyList()); + when(config.isKickPlayers()).thenReturn(true); + when(config.getKickMessage()).thenReturn("Blocked!"); + + VpnString mockVpnString = mock(VpnString.class); + when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!"); + when(messageHandler.getString(anyString())).thenReturn(mockVpnString); + + when(vpnExecutor.checkIp(anyString())) + .thenReturn( + CompletableFuture.completedFuture( + VPNResponse.builder() + .success(true) + .proxy(false) + .ip("127.0.0.1") + .method("N/A") + .countryName("N/A") + .city("N/A") + .build())); + + // Use reflection to set the private static INSTANCE field + Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); + instanceField.setAccessible(true); + instanceField.set(null, antiVPN); + + listener = new BukkitListener(); + } + + @AfterEach + public void tearDown() throws Exception { + // Reset the singleton + Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); + instanceField.setAccessible(true); + instanceField.set(null, null); + BukkitPlugin.pluginInstance = null; + + MockBukkit.unmock(); + } + + @Test + public void testLoginEventAllowed() throws Exception { + PlayerMock player = server.addPlayer("TestPlayer"); + InetAddress address = InetAddress.getByName("127.0.0.1"); + + mockCache( + "127.0.0.1", + new CheckResult( + VPNResponse.builder() + .success(true) + .proxy(false) + .ip("127.0.0.1") + .method("N/A") + .countryName("N/A") + .countryCode("N/A") + .city("N/A") + .build(), + ResultType.ALLOWED, + true)); + + PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address); + + listener.onLogin(event); + + assertEquals(PlayerLoginEvent.Result.ALLOWED, event.getResult()); + } + + @Test + public void testLoginPipelineProxyPlayerIsKickedWithoutErrors() throws Exception { + PlayerMock player = server.addPlayer("PipelineProxyPlayer"); + InetAddress address = InetAddress.getByName("1.1.1.1"); + + mockCache(); + PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address); + + assertDoesNotThrow(() -> listener.onLogin(event)); + + assertEquals(PlayerLoginEvent.Result.KICK_BANNED, event.getResult()); + assertEquals( + "Blocked!", + net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection() + .serialize(event.kickMessage())); + } + + @Test + public void testRunCommandDispatchesOnPrimaryThreadWhenCalledAsynchronously() { + RecordingServerMock recordingServer = (RecordingServerMock) server; + ExecutorService executor = Executors.newSingleThreadExecutor(); + + try { + CompletableFuture asyncCall = + CompletableFuture.runAsync(() -> listener.runCommand("antivpn-test &aok"), executor); + + assertDoesNotThrow(() -> asyncCall.get(5, TimeUnit.SECONDS)); + assertFalse( + recordingServer.commandDispatched(), + "Command should be scheduled, not dispatched asynchronously"); + + server.getScheduler().performOneTick(); + + assertTrue( + recordingServer.commandDispatched(), + "Scheduled command should be dispatched on the next server tick"); + assertTrue( + recordingServer.dispatchedOnPrimaryThread(), + "Command dispatch must happen on Bukkit's primary thread"); + assertEquals("antivpn-test §aok", recordingServer.dispatchedCommand()); + } finally { + executor.shutdownNow(); + } + } + + public static class TestPlugin extends JavaPlugin {} + + private static class RecordingServerMock extends ServerMock { + private final AtomicBoolean commandDispatched = new AtomicBoolean(); + private final AtomicBoolean dispatchedOnPrimaryThread = new AtomicBoolean(); + private final AtomicReference dispatchedCommand = new AtomicReference<>(); + + @Override + public boolean dispatchCommand(@NotNull CommandSender sender, @NotNull String commandLine) { + commandDispatched.set(true); + dispatchedOnPrimaryThread.set(isPrimaryThread()); + dispatchedCommand.set(commandLine); + return super.dispatchCommand(sender, commandLine); } - @AfterEach - public void tearDown() throws Exception { - // Reset the singleton - Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); - instanceField.setAccessible(true); - instanceField.set(null, null); - BukkitPlugin.pluginInstance = null; - - MockBukkit.unmock(); + private boolean commandDispatched() { + return commandDispatched.get(); } - @Test - public void testLoginEventAllowed() throws Exception { - PlayerMock player = server.addPlayer("TestPlayer"); - InetAddress address = InetAddress.getByName("127.0.0.1"); - - PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address); - - listener.onLogin(event); - - assertEquals(PlayerLoginEvent.Result.ALLOWED, event.getResult()); + private boolean dispatchedOnPrimaryThread() { + return dispatchedOnPrimaryThread.get(); } - @Test - public void testLoginEventBlocked() throws Exception { - PlayerMock player = server.addPlayer("ProxyPlayer"); - InetAddress address = InetAddress.getByName("1.1.1.1"); - - // Mock proxy response - when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture( - VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1") - .method("N/A").countryName("N/A").countryCode("N/A").city("N/A").build() - )); - - PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address); - - listener.onLogin(event); - - assertEquals(PlayerLoginEvent.Result.KICK_BANNED, event.getResult()); - assertEquals("Blocked!", net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(event.kickMessage())); - } - - @Test - public void testLoginPipelineProxyPlayerIsKickedWithoutErrors() throws Exception { - PlayerMock player = server.addPlayer("PipelineProxyPlayer"); - InetAddress address = InetAddress.getByName("1.1.1.1"); - - when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture( - VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1") - .method("N/A").countryName("N/A").countryCode("N/A").city("N/A").build() - )); - - PlayerLoginEvent event = new PlayerLoginEvent(player, "localhost", address); - - assertDoesNotThrow(() -> listener.onLogin(event)); - - assertEquals(PlayerLoginEvent.Result.KICK_BANNED, event.getResult()); - assertEquals("Blocked!", net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(event.kickMessage())); - } - - @Test - public void testRunCommandDispatchesOnPrimaryThreadWhenCalledAsynchronously() { - RecordingServerMock recordingServer = (RecordingServerMock) server; - ExecutorService executor = Executors.newSingleThreadExecutor(); - - try { - CompletableFuture asyncCall = CompletableFuture.runAsync( - () -> listener.runCommand("antivpn-test &aok"), - executor - ); - - assertDoesNotThrow(() -> asyncCall.get(5, TimeUnit.SECONDS)); - assertFalse(recordingServer.commandDispatched(), "Command should be scheduled, not dispatched asynchronously"); - - server.getScheduler().performOneTick(); - - assertTrue(recordingServer.commandDispatched(), "Scheduled command should be dispatched on the next server tick"); - assertTrue(recordingServer.dispatchedOnPrimaryThread(), "Command dispatch must happen on Bukkit's primary thread"); - assertEquals("antivpn-test §aok", recordingServer.dispatchedCommand()); - } finally { - executor.shutdownNow(); - } - } - - public static class TestPlugin extends JavaPlugin { - } - - private static class RecordingServerMock extends ServerMock { - private final AtomicBoolean commandDispatched = new AtomicBoolean(); - private final AtomicBoolean dispatchedOnPrimaryThread = new AtomicBoolean(); - private final AtomicReference dispatchedCommand = new AtomicReference<>(); - - @Override - public boolean dispatchCommand(@NotNull CommandSender sender, @NotNull String commandLine) { - commandDispatched.set(true); - dispatchedOnPrimaryThread.set(isPrimaryThread()); - dispatchedCommand.set(commandLine); - return super.dispatchCommand(sender, commandLine); - } - - private boolean commandDispatched() { - return commandDispatched.get(); - } - - private boolean dispatchedOnPrimaryThread() { - return dispatchedOnPrimaryThread.get(); - } - - private String dispatchedCommand() { - return dispatchedCommand.get(); - } + private String dispatchedCommand() { + return dispatchedCommand.get(); } + } } diff --git a/Bukkit/Plugin/src/test/java/dev/brighten/antivpn/bukkit/BukkitPlayerTest.java b/Bukkit/Plugin/src/test/java/dev/brighten/antivpn/bukkit/BukkitPlayerTest.java index 9d96caf..d5bcd3a 100644 --- a/Bukkit/Plugin/src/test/java/dev/brighten/antivpn/bukkit/BukkitPlayerTest.java +++ b/Bukkit/Plugin/src/test/java/dev/brighten/antivpn/bukkit/BukkitPlayerTest.java @@ -1,56 +1,56 @@ package dev.brighten.antivpn.bukkit; -import be.seeseemelk.mockbukkit.MockBukkit; -import be.seeseemelk.mockbukkit.ServerMock; -import be.seeseemelk.mockbukkit.entity.PlayerMock; -import org.bukkit.plugin.java.JavaPlugin; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import be.seeseemelk.mockbukkit.MockBukkit; +import be.seeseemelk.mockbukkit.ServerMock; +import be.seeseemelk.mockbukkit.entity.PlayerMock; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import org.bukkit.plugin.java.JavaPlugin; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + class BukkitPlayerTest { - private ServerMock server; + private ServerMock server; - @BeforeEach - void setUp() { - server = MockBukkit.mock(); + @BeforeEach + void setUp() { + server = MockBukkit.mock(); - BukkitPlugin pluginBootstrap = mock(BukkitPlugin.class); - JavaPlugin plugin = MockBukkit.createMockPlugin(); - when(pluginBootstrap.getPlugin()).thenReturn(plugin); - BukkitPlugin.pluginInstance = pluginBootstrap; - } + BukkitPlugin pluginBootstrap = mock(BukkitPlugin.class); + JavaPlugin plugin = MockBukkit.createMockPlugin(); + when(pluginBootstrap.getPlugin()).thenReturn(plugin); + BukkitPlugin.pluginInstance = pluginBootstrap; + } - @AfterEach - void tearDown() { - BukkitPlugin.pluginInstance = null; - MockBukkit.unmock(); - } + @AfterEach + void tearDown() { + BukkitPlugin.pluginInstance = null; + MockBukkit.unmock(); + } - @Test - void kickPlayerCalledFromAsyncContext_isScheduledAndExecutedOnMainThread() { - PlayerMock player = server.addPlayer("AsyncKickPlayer"); - BukkitPlayer bukkitPlayer = new BukkitPlayer(player); + @Test + void kickPlayerCalledFromAsyncContext_isScheduledAndExecutedOnMainThread() { + PlayerMock player = server.addPlayer("AsyncKickPlayer"); + BukkitPlayer bukkitPlayer = new BukkitPlayer(player); - assertTrue(player.isOnline()); + assertTrue(player.isOnline()); - CompletableFuture asyncKick = CompletableFuture.runAsync(() -> bukkitPlayer.kickPlayer("&cBlocked!")); - assertDoesNotThrow(() -> asyncKick.get(1, TimeUnit.SECONDS)); + CompletableFuture asyncKick = + CompletableFuture.runAsync(() -> bukkitPlayer.kickPlayer("&cBlocked!")); + assertDoesNotThrow(() -> asyncKick.get(1, TimeUnit.SECONDS)); - assertTrue(player.isOnline(), "Kick should be deferred to the server scheduler"); + assertTrue(player.isOnline(), "Kick should be deferred to the server scheduler"); - server.getScheduler().performTicks(1); + server.getScheduler().performTicks(1); - assertFalse(player.isOnline(), "Player should be kicked when scheduled task runs"); - } + assertFalse(player.isOnline(), "Player should be kicked when scheduled task runs"); + } } diff --git a/Bungee/BungeeLoader/build.gradle b/Bungee/BungeeLoader/build.gradle index cd97673..f5d591c 100644 --- a/Bungee/BungeeLoader/build.gradle +++ b/Bungee/BungeeLoader/build.gradle @@ -29,4 +29,8 @@ tasks.named('shadowJar') { dependsOn(':Bungee:BungeePlugin:shadowJar') } +tasks.named('compileJava') { + dependsOn(':Bungee:BungeePlugin:shadowJar') +} + tasks.build.dependsOn shadowJar diff --git a/Bungee/BungeeLoader/src/main/java/dev/brighten/antivpn/bungee/BungeeLoaderPlugin.java b/Bungee/BungeeLoader/src/main/java/dev/brighten/antivpn/bungee/BungeeLoaderPlugin.java index 16ae758..456afb4 100644 --- a/Bungee/BungeeLoader/src/main/java/dev/brighten/antivpn/bungee/BungeeLoaderPlugin.java +++ b/Bungee/BungeeLoader/src/main/java/dev/brighten/antivpn/bungee/BungeeLoaderPlugin.java @@ -22,31 +22,30 @@ import net.md_5.bungee.api.plugin.Plugin; public class BungeeLoaderPlugin extends Plugin { - private static final String JAR_NAME = "antivpn-bungee.jarinjar"; - private static final String SOURCE_NAME = "antivpn-source.jarinjar"; - private static final String BOOTSTRAP_CLASS = "dev.brighten.antivpn.bungee.BungeePlugin"; + private static final String JAR_NAME = "antivpn-bungee.jarinjar"; + private static final String SOURCE_NAME = "antivpn-source.jarinjar"; + private static final String BOOTSTRAP_CLASS = "dev.brighten.antivpn.bungee.BungeePlugin"; - private final LoaderBootstrap plugin; + private final LoaderBootstrap plugin; - public BungeeLoaderPlugin() { - JarInJarClassLoader loader = new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME); - this.plugin = loader.instantiatePlugin(BOOTSTRAP_CLASS, Plugin.class, this); - } + public BungeeLoaderPlugin() { + JarInJarClassLoader loader = + new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME); + this.plugin = loader.instantiatePlugin(BOOTSTRAP_CLASS, Plugin.class, this); + } - @Override - public void onLoad() { - this.plugin.onLoad(getDataFolder()); - } - - @Override - public void onEnable() { - this.plugin.onEnable(); - } - - @Override - public void onDisable() { - this.plugin.onDisable(); - } + @Override + public void onLoad() { + this.plugin.onLoad(getDataFolder()); + } + @Override + public void onEnable() { + this.plugin.onEnable(); + } + @Override + public void onDisable() { + this.plugin.onDisable(); + } } diff --git a/Bungee/BungeePlugin/build.gradle b/Bungee/BungeePlugin/build.gradle index 97d1a0d..d8ef6af 100644 --- a/Bungee/BungeePlugin/build.gradle +++ b/Bungee/BungeePlugin/build.gradle @@ -13,6 +13,7 @@ dependencies { testImplementation 'org.mockito:mockito-subclass:5.11.0' testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0' testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' + testImplementation testFixtures(project(':Common:Source')) } tasks.compileJava.dependsOn(':Common:Source:jar') diff --git a/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeeListener.java b/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeeListener.java index 2754a53..be94fa5 100644 --- a/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeeListener.java +++ b/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeeListener.java @@ -20,6 +20,9 @@ import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.api.*; import dev.brighten.antivpn.utils.MiscUtils; import dev.brighten.antivpn.utils.StringUtil; +import java.net.InetSocketAddress; +import java.util.UUID; +import java.util.logging.Level; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.api.event.PlayerDisconnectEvent; @@ -29,93 +32,123 @@ import net.md_5.bungee.api.scheduler.ScheduledTask; import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventPriority; -import java.net.InetSocketAddress; -import java.util.UUID; -import java.util.logging.Level; - public class BungeeListener extends VPNExecutor implements Listener { - private ScheduledTask cacheResetTask; + private ScheduledTask cacheResetTask; - @Override - public void registerListeners() { - BungeePlugin.pluginInstance.getProxy().getPluginManager() - .registerListener(BungeePlugin.pluginInstance.getPlugin(), this); + @Override + public void registerListeners() { + BungeePlugin.pluginInstance + .getProxy() + .getPluginManager() + .registerListener(BungeePlugin.pluginInstance.getPlugin(), this); + } + + @Override + public void log(Level level, String log, Object... objects) { + BungeePlugin.pluginInstance.getProxy().getLogger().log(Level.INFO, String.format(log, objects)); + } + + @Override + public void log(String log, Object... objects) { + log(Level.INFO, log, objects); + } + + @Override + public void logException(String message, Throwable ex) { + BungeePlugin.pluginInstance.getProxy().getLogger().log(Level.SEVERE, message, ex); + } + + @Override + public void runCommand(String command) { + BungeePlugin.pluginInstance + .getProxy() + .getPluginManager() + .dispatchCommand(BungeePlugin.pluginInstance.getProxy().getConsole(), command); + } + + @Override + public void disablePlugin() { + BungeePlugin.pluginInstance + .getProxy() + .getPluginManager() + .unregisterListeners(BungeePlugin.pluginInstance.getPlugin()); + if (cacheResetTask != null) { + cacheResetTask.cancel(); + cacheResetTask = null; } + BungeePlugin.pluginInstance + .getProxy() + .getPluginManager() + .unregisterCommands(BungeePlugin.pluginInstance.getPlugin()); + BungeePlugin.pluginInstance.onDisable(); + } - @Override - public void log(Level level, String log, Object... objects) { - BungeePlugin.pluginInstance.getProxy().getLogger().log(Level.INFO, String.format(log, objects)); - } + @EventHandler(priority = EventPriority.HIGH) + public void onListener(final PreLoginEvent event) { + APIPlayer player = + AntiVPN.getInstance() + .getPlayerExecutor() + .getPlayer(event.getConnection().getUniqueId()) + .orElseGet( + () -> { + UUID uuid = MiscUtils.lookupUUID(event.getConnection().getName()); + AntiVPN.getInstance() + .getExecutor() + .log( + Level.INFO, + "Getting offline player for %s with name %s", + event.getConnection().getUniqueId(), + uuid); - @Override - public void log(String log, Object... objects) { - log(Level.INFO, log, objects); - } - - @Override - public void logException(String message, Throwable ex) { - BungeePlugin.pluginInstance.getProxy().getLogger().log(Level.SEVERE, message, ex); - } - - @Override - public void runCommand(String command) { - BungeePlugin.pluginInstance.getProxy().getPluginManager() - .dispatchCommand(BungeePlugin.pluginInstance.getProxy().getConsole(), command); - } - - @Override - public void disablePlugin() { - BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterListeners(BungeePlugin.pluginInstance.getPlugin()); - if (cacheResetTask != null) { - cacheResetTask.cancel(); - cacheResetTask = null; - } - BungeePlugin.pluginInstance.getProxy().getPluginManager().unregisterCommands(BungeePlugin.pluginInstance.getPlugin()); - BungeePlugin.pluginInstance.onDisable(); - } - - @EventHandler(priority = EventPriority.HIGH) - public void onListener(final PreLoginEvent event) { - APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getConnection().getUniqueId()) - .orElseGet(() -> { - UUID uuid = MiscUtils.lookupUUID(event.getConnection().getName()); - AntiVPN.getInstance().getExecutor().log(Level.INFO, "Getting offline player for %s with name %s", - event.getConnection().getUniqueId(), uuid); - - return new OfflinePlayer(uuid, event.getConnection().getName(), - ((InetSocketAddress) event.getConnection().getSocketAddress()).getAddress()); + return new OfflinePlayer( + uuid, + event.getConnection().getName(), + ((InetSocketAddress) event.getConnection().getSocketAddress()).getAddress()); }); - CheckResult result = player.checkPlayer(); + CheckResult result = player.checkPlayer(); - if (!result.resultType().isShouldBlock()) return; + if (!result.resultType().isShouldBlock()) return; - if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { - return; - } - - event.setCancelled(true); - event.setReason(TextComponent.fromLegacy(StringUtil.varReplace(switch (result.resultType()) { - case DENIED_PROXY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() - .getKickMessage(), player, result.response()); - case DENIED_COUNTRY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() - .getCountryVanillaKickReason(), player, result.response()); - default -> "You were kicked by KauriVPN for an unknown reason!"; - }, player, result.response()))); + if (!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { + return; } - @EventHandler(priority = EventPriority.HIGH) - public void onJoin(LoginEvent event) { - if(event.isCancelled()) return; + event.setCancelled(true); + event.setReason( + TextComponent.fromLegacy( + StringUtil.varReplace( + switch (result.resultType()) { + case DENIED_PROXY -> + StringUtil.varReplace( + AntiVPN.getInstance().getVpnConfig().getKickMessage(), + player, + result.response()); + case DENIED_COUNTRY -> + StringUtil.varReplace( + AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(), + player, + result.response()); + default -> "You were kicked by KauriVPN for an unknown reason!"; + }, + player, + result.response()))); + } - // Handling player alerts on join - AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getConnection().getUniqueId()) - .ifPresent(APIPlayer::checkAlertsState); - } + @EventHandler(priority = EventPriority.HIGH) + public void onJoin(LoginEvent event) { + if (event.isCancelled()) return; - @EventHandler - public void onLeave(PlayerDisconnectEvent event) { - AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(event.getPlayer().getUniqueId()); - } + // Handling player alerts on join + AntiVPN.getInstance() + .getPlayerExecutor() + .getPlayer(event.getConnection().getUniqueId()) + .ifPresent(APIPlayer::checkAlertsState); + } + + @EventHandler + public void onLeave(PlayerDisconnectEvent event) { + AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(event.getPlayer().getUniqueId()); + } } diff --git a/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlayer.java b/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlayer.java index 072a3cf..c26c5cf 100644 --- a/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlayer.java +++ b/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlayer.java @@ -23,28 +23,28 @@ import net.md_5.bungee.api.connection.ProxiedPlayer; public class BungeePlayer extends APIPlayer { - private final ProxiedPlayer player; - public BungeePlayer(ProxiedPlayer player) { - super(player.getUniqueId(), player.getName(), player.getAddress().getAddress()); + private final ProxiedPlayer player; - this.player = player; - } + public BungeePlayer(ProxiedPlayer player) { + super(player.getUniqueId(), player.getName(), player.getAddress().getAddress()); + this.player = player; + } - @Override - public void sendMessage(String message) { - player.sendMessage(TextComponent.fromLegacyText(ChatColor - .translateAlternateColorCodes('&', message))); - } + @Override + public void sendMessage(String message) { + player.sendMessage( + TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', message))); + } - @Override - public void kickPlayer(String reason) { - player.disconnect(TextComponent.fromLegacyText(ChatColor - .translateAlternateColorCodes('&', reason))); - } + @Override + public void kickPlayer(String reason) { + player.disconnect( + TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', reason))); + } - @Override - public boolean hasPermission(String permission) { - return player.hasPermission(permission); - } + @Override + public boolean hasPermission(String permission) { + return player.hasPermission(permission); + } } diff --git a/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlayerExecutor.java b/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlayerExecutor.java index cb01b91..81db1c8 100644 --- a/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlayerExecutor.java +++ b/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlayerExecutor.java @@ -18,42 +18,42 @@ package dev.brighten.antivpn.bungee; import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.PlayerExecutor; -import net.md_5.bungee.api.connection.ProxiedPlayer; - import java.util.*; import java.util.stream.Collectors; +import net.md_5.bungee.api.connection.ProxiedPlayer; public class BungeePlayerExecutor implements PlayerExecutor { - private final Map cachedPlayers = new HashMap<>(); + private final Map cachedPlayers = new HashMap<>(); - @Override - public Optional getPlayer(String name) { - ProxiedPlayer player = BungeePlugin.pluginInstance.getProxy().getPlayer(name); + @Override + public Optional getPlayer(String name) { + ProxiedPlayer player = BungeePlugin.pluginInstance.getProxy().getPlayer(name); - if(player == null) return Optional.empty(); + if (player == null) return Optional.empty(); - return Optional.of(cachedPlayers.computeIfAbsent(player.getUniqueId(), key -> new BungeePlayer(player))); - } + return Optional.of( + cachedPlayers.computeIfAbsent(player.getUniqueId(), key -> new BungeePlayer(player))); + } - @Override - public Optional getPlayer(UUID uuid) { - ProxiedPlayer player = BungeePlugin.pluginInstance.getProxy().getPlayer(uuid); + @Override + public Optional getPlayer(UUID uuid) { + ProxiedPlayer player = BungeePlugin.pluginInstance.getProxy().getPlayer(uuid); - if(player == null) return Optional.empty(); + if (player == null) return Optional.empty(); - return Optional.of(cachedPlayers.computeIfAbsent(uuid, key -> new BungeePlayer(player))); - } + return Optional.of(cachedPlayers.computeIfAbsent(uuid, key -> new BungeePlayer(player))); + } - @Override - public void unloadPlayer(UUID uuid) { - this.cachedPlayers.remove(uuid); - } + @Override + public void unloadPlayer(UUID uuid) { + this.cachedPlayers.remove(uuid); + } - @Override - public List getOnlinePlayers() { - return BungeePlugin.pluginInstance.getProxy().getPlayers().stream() - .map(pl -> cachedPlayers.computeIfAbsent(pl.getUniqueId(), key -> new BungeePlayer(pl))) - .collect(Collectors.toList()); - } + @Override + public List getOnlinePlayers() { + return BungeePlugin.pluginInstance.getProxy().getPlayers().stream() + .map(pl -> cachedPlayers.computeIfAbsent(pl.getUniqueId(), key -> new BungeePlayer(pl))) + .collect(Collectors.toList()); + } } diff --git a/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlugin.java b/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlugin.java index 15bb2ac..0c8aacd 100644 --- a/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlugin.java +++ b/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/BungeePlugin.java @@ -24,79 +24,82 @@ import dev.brighten.antivpn.database.local.H2VPN; import dev.brighten.antivpn.database.mongo.MongoVPN; import dev.brighten.antivpn.database.sql.MySqlVPN; import dev.brighten.antivpn.loader.LoaderBootstrap; +import java.io.File; +import java.util.concurrent.TimeUnit; import lombok.Getter; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.plugin.Plugin; import org.bstats.bungeecord.Metrics; import org.bstats.charts.SimplePie; -import java.io.File; -import java.util.concurrent.TimeUnit; - public class BungeePlugin implements LoaderBootstrap { - public static BungeePlugin pluginInstance; - - @Getter - private File dataFolder; + public static BungeePlugin pluginInstance; - @Getter - private final Plugin plugin; + @Getter private File dataFolder; - public BungeePlugin(Plugin plugin) { - this.plugin = plugin; + @Getter private final Plugin plugin; + + public BungeePlugin(Plugin plugin) { + this.plugin = plugin; + } + + @Override + public void onLoad(File dataFolder) { + this.dataFolder = dataFolder; + } + + @Override + public void onEnable() { + pluginInstance = this; + + // Setting up config + ProxyServer.getInstance().getLogger().info("Loading config..."); + + // Loading plugin + ProxyServer.getInstance().getLogger().info("Starting AntiVPN services..."); + AntiVPN.start(new BungeeListener(), new BungeePlayerExecutor(), getDataFolder()); + + if (AntiVPN.getInstance().getVpnConfig().metrics()) { + ProxyServer.getInstance().getLogger().info("Starting bStats metrics..."); + Metrics metrics = new Metrics(getPlugin(), 12616); + metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType)); + ProxyServer.getInstance() + .getScheduler() + .schedule( + getPlugin(), + () -> AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0, + 10, + 10, + TimeUnit.MINUTES); } - @Override - public void onLoad(File dataFolder) { - this.dataFolder = dataFolder; + for (Command command : AntiVPN.getInstance().getCommands()) { + ProxyServer.getInstance() + .getPluginManager() + .registerCommand(getPlugin(), new BungeeCommand(command)); } + } - @Override - public void onEnable() { - pluginInstance = this; + @Override + public void onDisable() { + AntiVPN.getInstance().stop(); + } - //Setting up config - ProxyServer.getInstance().getLogger().info("Loading config..."); - - - //Loading plugin - ProxyServer.getInstance().getLogger().info("Starting AntiVPN services..."); - AntiVPN.start(new BungeeListener(), new BungeePlayerExecutor(), getDataFolder()); - - if(AntiVPN.getInstance().getVpnConfig().metrics()) { - ProxyServer.getInstance().getLogger().info("Starting bStats metrics..."); - Metrics metrics = new Metrics(getPlugin(), 12616); - metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType)); - ProxyServer.getInstance().getScheduler().schedule(getPlugin(), - () -> AntiVPN.getInstance().checked = AntiVPN.getInstance().detections = 0, - 10, 10, TimeUnit.MINUTES); - } - - for (Command command : AntiVPN.getInstance().getCommands()) { - ProxyServer.getInstance().getPluginManager().registerCommand(getPlugin(), new BungeeCommand(command)); - } + private String getDatabaseType() { + VPNDatabase database = AntiVPN.getInstance().getDatabase(); + if (database instanceof MySqlVPN) { + return "MySQL"; + } else if (database instanceof H2VPN) { + return "H2"; + } else if (database instanceof MongoVPN) { + return "MongoDB"; + } else { + return "No-Database"; } + } - @Override - public void onDisable() { - AntiVPN.getInstance().stop(); - } - - private String getDatabaseType() { - VPNDatabase database = AntiVPN.getInstance().getDatabase(); - if(database instanceof MySqlVPN) { - return "MySQL"; - } else if(database instanceof H2VPN) { - return "H2"; - } else if(database instanceof MongoVPN) { - return "MongoDB"; - } else { - return "No-Database"; - } - } - - public ProxyServer getProxy() { - return ProxyServer.getInstance(); - } + public ProxyServer getProxy() { + return ProxyServer.getInstance(); + } } diff --git a/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/command/BungeeCommand.java b/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/command/BungeeCommand.java index a762966..6c75c40 100644 --- a/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/command/BungeeCommand.java +++ b/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/command/BungeeCommand.java @@ -17,6 +17,8 @@ package dev.brighten.antivpn.bungee.command; import dev.brighten.antivpn.AntiVPN; +import java.util.Arrays; +import java.util.stream.IntStream; import lombok.val; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.CommandSender; @@ -24,72 +26,87 @@ import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.TabExecutor; -import java.util.Arrays; -import java.util.stream.IntStream; - public class BungeeCommand extends Command implements TabExecutor { - private final dev.brighten.antivpn.command.Command command; - public BungeeCommand(dev.brighten.antivpn.command.Command command) { - super(command.name(), command.permission(), command.aliases()); + private final dev.brighten.antivpn.command.Command command; - this.command = command; + public BungeeCommand(dev.brighten.antivpn.command.Command command) { + super(command.name(), command.permission(), command.aliases()); + + this.command = command; + } + + @Override + public void execute(CommandSender sender, String[] args) { + if (!sender.hasPermission("antivpn.command.*") && !sender.hasPermission(command.permission())) { + sender.sendMessage( + TextComponent.fromLegacyText( + ChatColor.translateAlternateColorCodes( + '&', + AntiVPN.getInstance() + .getMessageHandler() + .getString("no-permission") + .getMessage()))); + return; } - @Override - public void execute(CommandSender sender, String[] args) { - if(!sender.hasPermission("antivpn.command.*") - && !sender.hasPermission(command.permission())) { - sender.sendMessage(TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', - AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage()))); + val children = command.children(); + + if (children.length > 0 && args.length > 0) { + for (dev.brighten.antivpn.command.Command child : children) { + if (child.name().equalsIgnoreCase(args[0]) + || Arrays.stream(child.aliases()).anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) { + if (!sender.hasPermission("antivpn.command.*") + && !sender.hasPermission(child.permission())) { + sender.sendMessage( + TextComponent.fromLegacyText( + ChatColor.translateAlternateColorCodes( + '&', + AntiVPN.getInstance() + .getMessageHandler() + .getString("no-permission") + .getMessage()))); return; + } + + sender.sendMessage( + TextComponent.fromLegacyText( + ChatColor.translateAlternateColorCodes( + '&', + child.execute( + new BungeeCommandExecutor(sender), + IntStream.range(0, args.length - 1) + .mapToObj(i -> args[i + 1]) + .toArray(String[]::new))))); + return; } - - val children = command.children(); - - if(children.length > 0 && args.length > 0) { - for (dev.brighten.antivpn.command.Command child : children) { - if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases()) - .anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) { - if(!sender.hasPermission("antivpn.command.*") - && !sender.hasPermission(child.permission())) { - sender.sendMessage(TextComponent.fromLegacyText(ChatColor.translateAlternateColorCodes('&', - AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage()))); - return; - } - - sender.sendMessage(TextComponent - .fromLegacyText(ChatColor - .translateAlternateColorCodes('&', - child.execute(new BungeeCommandExecutor(sender), IntStream - .range(0, args.length - 1) - .mapToObj(i -> args[i + 1]).toArray(String[]::new))))); - return; - } - } - } - - - sender.sendMessage(TextComponent - .fromLegacyText(ChatColor - .translateAlternateColorCodes('&', - command.execute(new BungeeCommandExecutor(sender), args)))); + } } - @Override - public Iterable onTabComplete(CommandSender sender, String[] args) { - val children = command.children(); + sender.sendMessage( + TextComponent.fromLegacyText( + ChatColor.translateAlternateColorCodes( + '&', command.execute(new BungeeCommandExecutor(sender), args)))); + } - if(children.length > 0 && args.length > 0) { - for (dev.brighten.antivpn.command.Command child : children) { - if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases()) - .anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) { - return child.tabComplete(new BungeeCommandExecutor(sender), "alias", IntStream - .range(0, args.length - 1) - .mapToObj(i -> args[i + 1]).toArray(String[]::new)); - } - } + @Override + public Iterable onTabComplete(CommandSender sender, String[] args) { + val children = command.children(); + + if (children.length > 0 && args.length > 0) { + for (dev.brighten.antivpn.command.Command child : children) { + if (child.name().equalsIgnoreCase(args[0]) + || Arrays.stream(child.aliases()) + .anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) { + return child.tabComplete( + new BungeeCommandExecutor(sender), + "alias", + IntStream.range(0, args.length - 1) + .mapToObj(i -> args[i + 1]) + .toArray(String[]::new)); } - return command.tabComplete(new BungeeCommandExecutor(sender), "alias", args); + } } + return command.tabComplete(new BungeeCommandExecutor(sender), "alias", args); + } } diff --git a/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/command/BungeeCommandExecutor.java b/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/command/BungeeCommandExecutor.java index c0d3ebb..5c20d39 100644 --- a/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/command/BungeeCommandExecutor.java +++ b/Bungee/BungeePlugin/src/main/java/dev/brighten/antivpn/bungee/command/BungeeCommandExecutor.java @@ -19,39 +19,41 @@ package dev.brighten.antivpn.bungee.command; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.command.CommandExecutor; +import java.util.Optional; import lombok.RequiredArgsConstructor; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; -import java.util.Optional; - @RequiredArgsConstructor public class BungeeCommandExecutor implements CommandExecutor { - private final CommandSender sender; + private final CommandSender sender; - @Override - public void sendMessage(String message, Object... objects) { - sender.sendMessage(TextComponent.fromLegacyText(ChatColor - .translateAlternateColorCodes('&', String.format(message, objects)))); - } + @Override + public void sendMessage(String message, Object... objects) { + sender.sendMessage( + TextComponent.fromLegacyText( + ChatColor.translateAlternateColorCodes('&', String.format(message, objects)))); + } - @Override - public boolean hasPermission(String permission) { - return sender.hasPermission(permission); - } + @Override + public boolean hasPermission(String permission) { + return sender.hasPermission(permission); + } - @Override - public Optional getPlayer() { - if(!isPlayer()) return Optional.empty(); + @Override + public Optional getPlayer() { + if (!isPlayer()) return Optional.empty(); - return AntiVPN.getInstance().getPlayerExecutor().getPlayer(((ProxiedPlayer) sender).getUniqueId()); - } + return AntiVPN.getInstance() + .getPlayerExecutor() + .getPlayer(((ProxiedPlayer) sender).getUniqueId()); + } - @Override - public boolean isPlayer() { - return sender instanceof ProxiedPlayer; - } + @Override + public boolean isPlayer() { + return sender instanceof ProxiedPlayer; + } } diff --git a/Bungee/BungeePlugin/src/test/java/dev/brighten/antivpn/bungee/BungeeListenerTest.java b/Bungee/BungeePlugin/src/test/java/dev/brighten/antivpn/bungee/BungeeListenerTest.java index 0358da3..e9e1583 100644 --- a/Bungee/BungeePlugin/src/test/java/dev/brighten/antivpn/bungee/BungeeListenerTest.java +++ b/Bungee/BungeePlugin/src/test/java/dev/brighten/antivpn/bungee/BungeeListenerTest.java @@ -1,110 +1,114 @@ package dev.brighten.antivpn.bungee; +import static org.mockito.Mockito.*; + import dev.brighten.antivpn.AntiVPN; +import dev.brighten.antivpn.StandardTest; import dev.brighten.antivpn.api.PlayerExecutor; import dev.brighten.antivpn.api.VPNConfig; import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.message.MessageHandler; import dev.brighten.antivpn.message.VpnString; import dev.brighten.antivpn.web.objects.VPNResponse; +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.event.PreLoginEvent; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.lang.reflect.Field; -import java.net.InetSocketAddress; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; +public class BungeeListenerTest extends StandardTest { -import static org.mockito.Mockito.*; + private BungeeListener listener; + private VPNExecutor vpnExecutor; -public class BungeeListenerTest { + @BeforeEach + public void setUp() throws Exception { + AntiVPN antiVPN = mock(AntiVPN.class); + VPNConfig config = mock(VPNConfig.class); + PlayerExecutor playerExecutor = mock(PlayerExecutor.class); + vpnExecutor = mock(VPNExecutor.class); + MessageHandler messageHandler = mock(MessageHandler.class); - private BungeeListener listener; - private VPNExecutor vpnExecutor; + when(antiVPN.getVpnConfig()).thenReturn(config); + when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor); + when(antiVPN.getExecutor()).thenReturn(vpnExecutor); + when(antiVPN.getMessageHandler()).thenReturn(messageHandler); - @BeforeEach - public void setUp() throws Exception { - AntiVPN antiVPN = mock(AntiVPN.class); - VPNConfig config = mock(VPNConfig.class); - PlayerExecutor playerExecutor = mock(PlayerExecutor.class); - vpnExecutor = mock(VPNExecutor.class); - MessageHandler messageHandler = mock(MessageHandler.class); + when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty()); + when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList()); + when(config.getCountryList()).thenReturn(java.util.Collections.emptyList()); + when(config.isKickPlayers()).thenReturn(true); + when(config.getKickMessage()).thenReturn("Blocked!"); - when(antiVPN.getVpnConfig()).thenReturn(config); - when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor); - when(antiVPN.getExecutor()).thenReturn(vpnExecutor); - when(antiVPN.getMessageHandler()).thenReturn(messageHandler); + VpnString mockVpnString = mock(VpnString.class); + when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!"); + when(messageHandler.getString(anyString())).thenReturn(mockVpnString); - when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty()); - when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList()); - when(config.getCountryList()).thenReturn(java.util.Collections.emptyList()); - when(config.isKickPlayers()).thenReturn(true); - when(config.getKickMessage()).thenReturn("Blocked!"); + when(vpnExecutor.checkIp(anyString())) + .thenReturn( + CompletableFuture.completedFuture( + VPNResponse.builder() + .success(true) + .proxy(false) + .ip("127.0.0.1") + .method("N/A") + .countryName("N/A") + .city("N/A") + .build())); - VpnString mockVpnString = mock(VpnString.class); - when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!"); - when(messageHandler.getString(anyString())).thenReturn(mockVpnString); + // Use reflection to set the private static INSTANCE field + Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); + instanceField.setAccessible(true); + instanceField.set(null, antiVPN); - when(vpnExecutor.checkIp(anyString())).thenReturn(CompletableFuture.completedFuture( - VPNResponse.builder().success(true).proxy(false).ip("127.0.0.1") - .method("N/A").countryName("N/A").city("N/A").build() - )); + listener = new BungeeListener(); + } - // Use reflection to set the private static INSTANCE field - Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); - instanceField.setAccessible(true); - instanceField.set(null, antiVPN); + @AfterEach + public void tearDown() throws Exception { + // Reset the singleton + Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); + instanceField.setAccessible(true); + instanceField.set(null, null); + } - listener = new BungeeListener(); - } + @Test + public void testPreLoginEventAllowed() { + PreLoginEvent event = mock(PreLoginEvent.class); + PendingConnection connection = mock(PendingConnection.class); - @AfterEach - public void tearDown() throws Exception { - // Reset the singleton - Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); - instanceField.setAccessible(true); - instanceField.set(null, null); - } + when(event.getConnection()).thenReturn(connection); + when(connection.getUniqueId()).thenReturn(UUID.randomUUID()); + when(connection.getName()).thenReturn("TestPlayer"); + when(connection.getSocketAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 12345)); - @Test - public void testPreLoginEventAllowed() { - PreLoginEvent event = mock(PreLoginEvent.class); - PendingConnection connection = mock(PendingConnection.class); + listener.onListener(event); - when(event.getConnection()).thenReturn(connection); - when(connection.getUniqueId()).thenReturn(UUID.randomUUID()); - when(connection.getName()).thenReturn("TestPlayer"); - when(connection.getSocketAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 12345)); + verify(event, never()).setCancelled(true); + } - listener.onListener(event); + @Test + public void testPreLoginEventBlocked() throws NoSuchFieldException, IllegalAccessException { + PreLoginEvent event = mock(PreLoginEvent.class); + PendingConnection connection = mock(PendingConnection.class); - verify(event, never()).setCancelled(true); - } + UUID uuid = UUID.randomUUID(); + when(event.getConnection()).thenReturn(connection); + when(connection.getUniqueId()).thenReturn(uuid); + when(connection.getName()).thenReturn("ProxyPlayer"); + when(connection.getSocketAddress()).thenReturn(new InetSocketAddress("1.1.1.1", 12345)); - @Test - public void testPreLoginEventBlocked() { - PreLoginEvent event = mock(PreLoginEvent.class); - PendingConnection connection = mock(PendingConnection.class); + // Mock proxy response + mockCache(); - UUID uuid = UUID.randomUUID(); - when(event.getConnection()).thenReturn(connection); - when(connection.getUniqueId()).thenReturn(uuid); - when(connection.getName()).thenReturn("ProxyPlayer"); - when(connection.getSocketAddress()).thenReturn(new InetSocketAddress("1.1.1.1", 12345)); + listener.onListener(event); - // Mock proxy response - when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture( - VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1") - .method("N/A").countryName("N/A").countryCode("N/A").city("N/A").build() - )); - - listener.onListener(event); - - verify(event).setCancelled(true); - verify(event).setReason(any()); - } + verify(event).setCancelled(true); + verify(event).setReason(any()); + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d1e4fc..bc85553 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.10.1.2] 2026-05-03 + +### Fixed +- Kick spam on non-cached VPN detection events. + +### Changed +- Project cleanup and performance improvements. + ## [1.10.1.1] 2026-04-29 ### Fixed diff --git a/Common/Source/build.gradle b/Common/Source/build.gradle index 2f22f37..31c3d8e 100644 --- a/Common/Source/build.gradle +++ b/Common/Source/build.gradle @@ -1,5 +1,6 @@ plugins { id 'com.gradleup.shadow' + id 'java-test-fixtures' } dependencies { @@ -7,12 +8,12 @@ dependencies { implementation 'org.ow2.asm:asm-commons:9.8' implementation 'org.yaml:snakeyaml:2.2' implementation 'org.jetbrains:annotations:26.0.2' - + compileOnly 'com.mysql:mysql-connector-j:9.3.0' compileOnly 'com.h2database:h2:2.4.240' implementation'com.github.ben-manes.caffeine:caffeine:3.1.8' compileOnly 'org.mongodb:mongo-java-driver:3.12.14' - + testImplementation 'org.mockito:mockito-core:5.11.0' testImplementation "org.junit.jupiter:junit-jupiter:5.8.1" testImplementation "org.testcontainers:testcontainers:2.0.4" @@ -24,6 +25,7 @@ dependencies { testImplementation 'com.h2database:h2:2.4.240' testImplementation 'org.mongodb:mongo-java-driver:3.12.14' testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' + testFixturesImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' } shadowJar { diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java b/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java index ce7c4a2..a14c152 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/AntiVPN.java @@ -33,10 +33,6 @@ import dev.brighten.antivpn.utils.MiscUtils; import dev.brighten.antivpn.utils.config.Configuration; import dev.brighten.antivpn.utils.config.ConfigurationProvider; import dev.brighten.antivpn.utils.config.YamlConfiguration; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Setter; - import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -44,223 +40,253 @@ import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.List; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; @Getter @Setter(AccessLevel.PRIVATE) -@MavenLibrary(groupId = "com.h2database", artifactId ="h2", version = "2.4.240") +@MavenLibrary(groupId = "com.h2database", artifactId = "h2", version = "2.4.240") @MavenLibrary(groupId = "org.mongodb", artifactId = "mongo-java-driver", version = "3.12.14") -@MavenLibrary( - groupId = "com.mysql", - artifactId = "mysql-connector-j", - version = "9.3.0" -) +@MavenLibrary(groupId = "com.mysql", artifactId = "mysql-connector-j", version = "9.3.0") public class AntiVPN { - private static AntiVPN INSTANCE; - private VPNConfig vpnConfig; - private VPNExecutor executor; - private PlayerExecutor playerExecutor; - private VPNDatabase database; - private MessageHandler messageHandler; - private Configuration config; - private List commands = new ArrayList<>(); - public int detections, checked; - private File pluginFolder; + private static AntiVPN INSTANCE; + private VPNConfig vpnConfig; + private VPNExecutor executor; + private PlayerExecutor playerExecutor; + private VPNDatabase database; + private MessageHandler messageHandler; + private Configuration config; + private List commands = new ArrayList<>(); + public int detections, checked; + private File pluginFolder; - public static void start(VPNExecutor executor, PlayerExecutor playerExecutor, File pluginFolder) { - //Initializing + public static void start(VPNExecutor executor, PlayerExecutor playerExecutor, File pluginFolder) { + // Initializing - INSTANCE = new AntiVPN(); + INSTANCE = new AntiVPN(); - INSTANCE.pluginFolder = pluginFolder; - INSTANCE.executor = executor; - INSTANCE.playerExecutor = playerExecutor; + INSTANCE.pluginFolder = pluginFolder; + INSTANCE.executor = executor; + INSTANCE.playerExecutor = playerExecutor; - LibraryLoader.loadAll(INSTANCE); + LibraryLoader.loadAll(INSTANCE); - try { - File configFile = new File(pluginFolder, "config.yml"); - if(!configFile.exists()){ - if(configFile.getParentFile().mkdirs()) { - AntiVPN.getInstance().getExecutor().log("Created plugin folder!"); - } - MiscUtils.copy(INSTANCE.getResource( "config.yml"), configFile); - } - INSTANCE.config = ConfigurationProvider.getProvider(YamlConfiguration.class) - .load(configFile); - } catch (IOException e) { - AntiVPN.getInstance().getExecutor().logException("Could not load config.yml, plugin disabling...", e); - executor.disablePlugin(); - return; + try { + File configFile = new File(pluginFolder, "config.yml"); + if (!configFile.exists()) { + if (configFile.getParentFile().mkdirs()) { + AntiVPN.getInstance().getExecutor().log("Created plugin folder!"); } + MiscUtils.copy(INSTANCE.getResource("config.yml"), configFile); + } + INSTANCE.config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile); + } catch (IOException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not load config.yml, plugin disabling...", e); + executor.disablePlugin(); + return; + } - INSTANCE.vpnConfig = new VPNConfig(); + INSTANCE.vpnConfig = new VPNConfig(); - INSTANCE.executor.registerListeners(); - INSTANCE.vpnConfig.update(); + INSTANCE.executor.registerListeners(); + INSTANCE.vpnConfig.update(); - INSTANCE.messageHandler = new MessageHandler(); + INSTANCE.messageHandler = new MessageHandler(); - try { - switch(INSTANCE.vpnConfig.getDatabaseType().toLowerCase()) { - case "h2": - case "local": - case "flatfile": { - AntiVPN.getInstance().getExecutor().log("Using databaseType H2..."); - INSTANCE.database = new H2VPN(); - INSTANCE.database.init(); - break; - } - case "mysql": - case "sql": { - AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL..."); - INSTANCE.database = new MySqlVPN(); - INSTANCE.database.init(); - break; - } - case "mongo": - case "mongodb": - case "mongod": { - INSTANCE.database = new MongoVPN(); - INSTANCE.database.init(); - break; - } - default: { - AntiVPN.getInstance().getExecutor().log("Could not find database type \"" + INSTANCE.vpnConfig.getDatabaseType() + "\". " + - "Options: [MySQL]"); - break; - } - } - } catch (Exception e) { - AntiVPN.getInstance().getExecutor().logException("Could not initialize database, plugin disabling...", e); - executor.disablePlugin(); - return; - } + try { + switch (INSTANCE.vpnConfig.getDatabaseType().toLowerCase()) { + case "h2": + case "local": + case "flatfile": + { + AntiVPN.getInstance().getExecutor().log("Using databaseType H2..."); + INSTANCE.database = new H2VPN(); + INSTANCE.database.init(); + break; + } + case "mysql": + case "sql": + { + AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL..."); + INSTANCE.database = new MySqlVPN(); + INSTANCE.database.init(); + break; + } + case "mongo": + case "mongodb": + case "mongod": + { + INSTANCE.database = new MongoVPN(); + INSTANCE.database.init(); + break; + } + default: + { + AntiVPN.getInstance() + .getExecutor() + .log( + "Could not find database type \"" + + INSTANCE.vpnConfig.getDatabaseType() + + "\". " + + "Options: [MySQL]"); + break; + } + } + } catch (Exception e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not initialize database, plugin disabling...", e); + executor.disablePlugin(); + return; + } - //Registering commands - INSTANCE.registerCommands(); + // Registering commands + INSTANCE.registerCommands(); - //Turning on alerts of players who are already online. - playerExecutor.getOnlinePlayers().forEach(player -> { - //We want to make sure they even have permission to see alerts before we make a bunch - //of unnecessary database queries. - if(player.hasPermission("antivpn.command.alerts")) { - //Running database check for enabled alerts. + // Turning on alerts of players who are already online. + playerExecutor + .getOnlinePlayers() + .forEach( + player -> { + // We want to make sure they even have permission to see alerts before we make a bunch + // of unnecessary database queries. + if (player.hasPermission("antivpn.command.alerts")) { + // Running database check for enabled alerts. INSTANCE.database.alertsState(player.getUuid(), player::setAlertsEnabled); - } - }); + } + }); - AntiVPN.getInstance().getMessageHandler().initStrings(vpnString -> new ConfigDefault<> - (vpnString.getDefaultMessage(), "messages." + vpnString.getKey(), AntiVPN.getInstance()) - .get()); - AntiVPN.getInstance().getMessageHandler().reloadStrings(); + AntiVPN.getInstance() + .getMessageHandler() + .initStrings( + vpnString -> + new ConfigDefault<>( + vpnString.getDefaultMessage(), + "messages." + vpnString.getKey(), + AntiVPN.getInstance()) + .get()); + AntiVPN.getInstance().getMessageHandler().reloadStrings(); - // Starting kick checks - AntiVPN.getInstance().getExecutor().startKickChecks(); - } + // Starting kick checks + AntiVPN.getInstance().getExecutor().startKickChecks(); + } - public InputStream getResource(String filename) { - if (filename == null) { - throw new IllegalArgumentException("Filename cannot be null"); + public InputStream getResource(String filename) { + if (filename == null) { + throw new IllegalArgumentException("Filename cannot be null"); + } else { + try { + URL url = executor.getClass().getClassLoader().getResource(filename); + if (url == null) { + return null; } else { - try { - URL url = executor.getClass().getClassLoader().getResource(filename); - if (url == null) { - return null; - } else { - URLConnection connection = url.openConnection(); - connection.setUseCaches(false); - return connection.getInputStream(); - } - } catch (IOException var4) { - return null; - } + URLConnection connection = url.openConnection(); + connection.setUseCaches(false); + return connection.getInputStream(); + } + } catch (IOException var4) { + return null; + } + } + } + + public void stop() { + if (database instanceof H2VPN) { + database.shutdown(); + + // Try to deregister driver + try { + java.sql.Driver driver = java.sql.DriverManager.getDriver("jdbc:h2:"); + if (driver != null) { + java.sql.DriverManager.deregisterDriver(driver); + } + } catch (Exception e) { + // Log but don't throw + executor.log("Failed to deregister H2 driver: " + e.getMessage()); + } + } + if (executor != null && executor.getThreadExecutor() != null) { + executor.getThreadExecutor().shutdown(); + } + if (database != null) database.shutdown(); + + INSTANCE = null; + } + + public void reloadDatabase() { + database.shutdown(); + + switch (AntiVPN.getInstance().getVpnConfig().getDatabaseType().toLowerCase()) { + case "h2": + case "local": + case "flatfile": + { + AntiVPN.getInstance().getExecutor().log("Using databaseType H2..."); + INSTANCE.database = new H2VPN(); + INSTANCE.database.init(); + break; + } + case "mysql": + case "sql": + { + AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL..."); + INSTANCE.database = new MySqlVPN(); + INSTANCE.database.init(); + break; + } + case "mongo": + case "mongodb": + case "mongod": + { + INSTANCE.database = new MongoVPN(); + INSTANCE.database.init(); + break; + } + default: + { + AntiVPN.getInstance() + .getExecutor() + .log( + "Could not find database type \"" + + INSTANCE.vpnConfig.getDatabaseType() + + "\". " + + "Options: [MySQL]"); + break; } } + } - public void stop() { - if (database instanceof H2VPN) { - database.shutdown(); + public static AntiVPN getInstance() { + assert INSTANCE != null : "AntiVPN has not been initialized!"; - // Try to deregister driver - try { - java.sql.Driver driver = java.sql.DriverManager.getDriver("jdbc:h2:"); - if (driver != null) { - java.sql.DriverManager.deregisterDriver(driver); - } - } catch (Exception e) { - // Log but don't throw - executor.log("Failed to deregister H2 driver: " + e.getMessage()); - } - } - if (executor != null && executor.getThreadExecutor() != null) { - executor.getThreadExecutor().shutdown(); - } - if(database != null) database.shutdown(); + return INSTANCE; + } - INSTANCE = null; + public void saveConfig() { + try { + ConfigurationProvider.getProvider(YamlConfiguration.class) + .save(getConfig(), new File(pluginFolder.getPath() + File.separator + "config.yml")); + } catch (IOException e) { + AntiVPN.getInstance().getExecutor().logException(e); } + } - public void reloadDatabase() { - database.shutdown(); + public void reloadConfig() { + try { - switch(AntiVPN.getInstance().getVpnConfig().getDatabaseType().toLowerCase()) { - case "h2": - case "local": - case "flatfile": { - AntiVPN.getInstance().getExecutor().log("Using databaseType H2..."); - INSTANCE.database = new H2VPN(); - INSTANCE.database.init(); - break; - } - case "mysql": - case "sql":{ - AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL..."); - INSTANCE.database = new MySqlVPN(); - INSTANCE.database.init(); - break; - } - case "mongo": - case "mongodb": - case "mongod": { - INSTANCE.database = new MongoVPN(); - INSTANCE.database.init(); - break; - } - default: { - AntiVPN.getInstance().getExecutor().log("Could not find database type \"" + INSTANCE.vpnConfig.getDatabaseType() + "\". " + - "Options: [MySQL]"); - break; - } - } + config = + ConfigurationProvider.getProvider(YamlConfiguration.class) + .load(new File(pluginFolder.getPath() + File.separator + "config.yml")); + } catch (IOException e) { + throw new RuntimeException(e); } + } - public static AntiVPN getInstance() { - assert INSTANCE != null: "AntiVPN has not been initialized!"; - - return INSTANCE; - } - - public void saveConfig() { - try { - ConfigurationProvider.getProvider(YamlConfiguration.class) - .save(getConfig(), new File(pluginFolder.getPath() + File.separator + "config.yml")); - } catch (IOException e) { - AntiVPN.getInstance().getExecutor().logException(e); - } - } - - public void reloadConfig() { - try { - - config = ConfigurationProvider.getProvider(YamlConfiguration.class) - .load(new File(pluginFolder.getPath() + File.separator + "config.yml")); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private void registerCommands() { - commands.add(new AntiVPNCommand()); - } + private void registerCommands() { + commands.add(new AntiVPNCommand()); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/api/APIPlayer.java b/Common/Source/src/main/java/dev/brighten/antivpn/api/APIPlayer.java index 97554b0..8311b74 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/api/APIPlayer.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/api/APIPlayer.java @@ -20,126 +20,152 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.message.VpnString; -import lombok.Getter; -import lombok.Setter; - import java.net.InetAddress; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.logging.Level; +import lombok.Getter; +import lombok.Setter; @Getter public abstract class APIPlayer { - private final UUID uuid; - private final String name; - private final InetAddress ip; - @Setter - private boolean alertsEnabled; + private final UUID uuid; + private final String name; + private final InetAddress ip; + @Setter private boolean alertsEnabled; - private static final Cache checkResultCache = Caffeine.newBuilder() - .expireAfterWrite(5, TimeUnit.MINUTES) - .maximumSize(2000) - .build(); + public static final Cache checkResultCache = + Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).maximumSize(2000).build(); - public APIPlayer(UUID uuid, String name, InetAddress ip) { - this.uuid = uuid; - this.name = name; - this.ip = ip; + public APIPlayer(UUID uuid, String name, InetAddress ip) { + this.uuid = uuid; + this.name = name; + this.ip = ip; + } + + public abstract void sendMessage(String message); + + public abstract void kickPlayer(String reason); + + public abstract boolean hasPermission(String permission); + + public void updateAlertsState() { + // Updating into database so its synced across servers and saved on logout. + AntiVPN.getInstance().getDatabase().updateAlertsState(uuid, alertsEnabled); + + sendMessage( + AntiVPN.getInstance() + .getMessageHandler() + .getString("command-alerts-toggled") + .getFormattedMessage(new VpnString.Var<>("state", alertsEnabled))); + } + + public void checkAlertsState() { + AntiVPN.getInstance() + .getExecutor() + .getThreadExecutor() + .execute( + () -> + AntiVPN.getInstance() + .getDatabase() + .alertsState( + uuid, + state -> { + if (state) { + alertsEnabled = true; + updateAlertsState(); + } + })); + } + + /*** + * The result is only returned if it is cached. Otherwise, there is an asynchronous call to the API + * and will handle kicking the player if necessary. + * @return CheckResult - The cached result of the check if it exists. + */ + public CheckResult checkPlayer() { + if (hasPermission("antivpn.bypass") // Has bypass permission + // Is exempt + || (uuid != null && AntiVPN.getInstance().getExecutor().isWhitelisted(uuid)) + // Or has a name that starts with a certain prefix. This is for Bedrock exempting. + || AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress() + "/32") + || AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream() + .anyMatch(name::startsWith)) { + return new CheckResult(null, ResultType.WHITELISTED, false); } - public abstract void sendMessage(String message); + CheckResult cachedResult = checkResultCache.getIfPresent(ip.getHostAddress()); - public abstract void kickPlayer(String reason); - - public abstract boolean hasPermission(String permission); - - public void updateAlertsState() { - //Updating into database so its synced across servers and saved on logout. - AntiVPN.getInstance().getDatabase().updateAlertsState(uuid, alertsEnabled); - - sendMessage(AntiVPN.getInstance().getMessageHandler() - .getString("command-alerts-toggled") - .getFormattedMessage(new VpnString.Var<>("state", alertsEnabled))); - } - - public void checkAlertsState() { - AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> - AntiVPN.getInstance().getDatabase().alertsState(uuid, state -> { - if(state) { - alertsEnabled = true; - updateAlertsState(); - } - }) - ); - } - - /*** - * The result is only returned if it is cached. Otherwise, there is an asynchronous call to the API - * and will handle kicking the player if necessary. - * @return CheckResult - The cached result of the check if it exists. - */ - public CheckResult checkPlayer() { - if (hasPermission("antivpn.bypass") //Has bypass permission - //Is exempt - || (uuid != null && AntiVPN.getInstance().getExecutor().isWhitelisted(uuid)) - //Or has a name that starts with a certain prefix. This is for Bedrock exempting. - || AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress() + "/32") - || AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream() - .anyMatch(name::startsWith)) { - return new CheckResult(null, ResultType.WHITELISTED, false); + if (cachedResult != null) { + if (cachedResult.response().getIp().equals(ip.getHostAddress())) { + AntiVPN.getInstance() + .getExecutor() + .log( + Level.FINE, + "Cached result for " + ip.getHostAddress() + " is " + cachedResult.resultType()); + if (cachedResult.resultType().isShouldBlock()) { + AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(cachedResult, this); } - - CheckResult cachedResult = checkResultCache.getIfPresent(ip.getHostAddress()); - - if(cachedResult != null) { - if(cachedResult.response().getIp().equals(ip.getHostAddress())) { - AntiVPN.getInstance().getExecutor().log(Level.FINE, "Cached result for " + ip.getHostAddress() + " is " + cachedResult.resultType()); - if(cachedResult.resultType().isShouldBlock()) { - AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(cachedResult, this); - } - return cachedResult; - } - } - - AntiVPN.getInstance().getExecutor().checkIp(ip.getHostAddress()) - .thenAccept(result -> { - if(!result.isSuccess()) { - AntiVPN.getInstance().getExecutor().log(Level.WARNING, "The API query was not a success! " + - "You may need to upgrade your license on " + - "https://funkemunky.cc/shop"); - return; - } - // If the countryList() size is zero, no need to check. - // Running country check first - CheckResult checkResult; - if (!AntiVPN.getInstance().getVpnConfig().getCountryList().isEmpty() - && !((uuid != null && AntiVPN.getInstance().getExecutor() - .isWhitelisted(uuid)) - //Or has a name that starts with a certain prefix. This is for Bedrock exempting. - || AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress() + "/32")) - // This bit of code will decide whether or not to kick the player - // If it contains the code and it is set to whitelist, it will not kick - // as they are equal and vise versa. However, if the contains does not match - // the state, it will kick. - && AntiVPN.getInstance().getVpnConfig().getCountryList() - .contains(result.getCountryCode()) - != AntiVPN.getInstance().getVpnConfig().getWhitelistCountries()) { - //Using our built in kicking system if no commands are configured - checkResult = new CheckResult(result, ResultType.DENIED_COUNTRY, false); - } else if (result.isProxy()) { - checkResult = new CheckResult(result, ResultType.DENIED_PROXY, false); - } else { - checkResult = new CheckResult(result, ResultType.ALLOWED, false); - } - - AntiVPN.getInstance().getExecutor().log(Level.FINE, "Result for " + ip.getHostAddress() + " is " + checkResult.resultType()); - - checkResultCache.put(ip.getHostAddress(), new CheckResult(checkResult.response(), checkResult.resultType(), true)); - if(checkResult.resultType().isShouldBlock()) { - AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(checkResult, this); - } - AntiVPN.getInstance().checked++; - }); - return new CheckResult(null, ResultType.UNKNOWN, false); + return cachedResult; + } } + + AntiVPN.getInstance() + .getExecutor() + .checkIp(ip.getHostAddress()) + .thenAccept( + result -> { + if (!result.isSuccess()) { + AntiVPN.getInstance() + .getExecutor() + .log( + Level.WARNING, + "The API query was not a success! " + + "You may need to upgrade your license on " + + "https://funkemunky.cc/shop"); + return; + } + // If the countryList() size is zero, no need to check. + // Running country check first + CheckResult checkResult; + if (!AntiVPN.getInstance().getVpnConfig().getCountryList().isEmpty() + && !((uuid != null && AntiVPN.getInstance().getExecutor().isWhitelisted(uuid)) + // Or has a name that starts with a certain prefix. This is for Bedrock + // exempting. + || AntiVPN.getInstance() + .getExecutor() + .isWhitelisted(ip.getHostAddress() + "/32")) + // This bit of code will decide whether or not to kick the player + // If it contains the code and it is set to whitelist, it will not kick + // as they are equal and vise versa. However, if the contains does not match + // the state, it will kick. + && AntiVPN.getInstance() + .getVpnConfig() + .getCountryList() + .contains(result.getCountryCode()) + != AntiVPN.getInstance().getVpnConfig().getWhitelistCountries()) { + // Using our built in kicking system if no commands are configured + checkResult = new CheckResult(result, ResultType.DENIED_COUNTRY, false); + } else if (result.isProxy()) { + checkResult = new CheckResult(result, ResultType.DENIED_PROXY, false); + } else { + checkResult = new CheckResult(result, ResultType.ALLOWED, false); + } + + AntiVPN.getInstance() + .getExecutor() + .log( + Level.FINE, + "Result for " + ip.getHostAddress() + " is " + checkResult.resultType()); + + checkResultCache.put( + ip.getHostAddress(), + new CheckResult(checkResult.response(), checkResult.resultType(), true)); + if (checkResult.resultType().isShouldBlock()) { + AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(checkResult, this); + } + AntiVPN.getInstance().checked++; + }); + return new CheckResult(null, ResultType.UNKNOWN, false); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/api/CheckResult.java b/Common/Source/src/main/java/dev/brighten/antivpn/api/CheckResult.java index d10c57b..a166389 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/api/CheckResult.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/api/CheckResult.java @@ -18,5 +18,4 @@ package dev.brighten.antivpn.api; import dev.brighten.antivpn.web.objects.VPNResponse; -public record CheckResult(VPNResponse response, ResultType resultType, boolean isFromCache) { -} +public record CheckResult(VPNResponse response, ResultType resultType, boolean isFromCache) {} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/api/OfflinePlayer.java b/Common/Source/src/main/java/dev/brighten/antivpn/api/OfflinePlayer.java index cec0072..392de65 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/api/OfflinePlayer.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/api/OfflinePlayer.java @@ -21,22 +21,18 @@ import java.util.UUID; public class OfflinePlayer extends APIPlayer { - public OfflinePlayer(UUID uuid, String name, InetAddress ip) { - super(uuid, name, ip); - } + public OfflinePlayer(UUID uuid, String name, InetAddress ip) { + super(uuid, name, ip); + } - @Override - public void sendMessage(String message) { + @Override + public void sendMessage(String message) {} - } + @Override + public void kickPlayer(String reason) {} - @Override - public void kickPlayer(String reason) { - - } - - @Override - public boolean hasPermission(String permission) { - return false; - } + @Override + public boolean hasPermission(String permission) { + return false; + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/api/PlayerExecutor.java b/Common/Source/src/main/java/dev/brighten/antivpn/api/PlayerExecutor.java index e5fba0c..74e5196 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/api/PlayerExecutor.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/api/PlayerExecutor.java @@ -22,11 +22,11 @@ import java.util.UUID; public interface PlayerExecutor { - Optional getPlayer(String name); + Optional getPlayer(String name); - Optional getPlayer(UUID uuid); + Optional getPlayer(UUID uuid); - void unloadPlayer(UUID uuid); + void unloadPlayer(UUID uuid); - List getOnlinePlayers(); + List getOnlinePlayers(); } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/api/ResultType.java b/Common/Source/src/main/java/dev/brighten/antivpn/api/ResultType.java index 3047385..0bc501d 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/api/ResultType.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/api/ResultType.java @@ -19,17 +19,16 @@ package dev.brighten.antivpn.api; import lombok.Getter; public enum ResultType { - ALLOWED(false), - WHITELISTED(false), - DENIED_COUNTRY(true), - DENIED_PROXY(true), - API_FAILURE(false), - UNKNOWN(false); + ALLOWED(false), + WHITELISTED(false), + DENIED_COUNTRY(true), + DENIED_PROXY(true), + API_FAILURE(false), + UNKNOWN(false); - @Getter - private final boolean shouldBlock; + @Getter private final boolean shouldBlock; - ResultType(boolean shouldBlock) { - this.shouldBlock = shouldBlock; - } + ResultType(boolean shouldBlock) { + this.shouldBlock = shouldBlock; + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/api/VPNConfig.java b/Common/Source/src/main/java/dev/brighten/antivpn/api/VPNConfig.java index 86e84e0..8513747 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/api/VPNConfig.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/api/VPNConfig.java @@ -18,208 +18,203 @@ package dev.brighten.antivpn.api; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.utils.ConfigDefault; -import lombok.Getter; - import java.util.ArrayList; import java.util.Collections; import java.util.List; +import lombok.Getter; public class VPNConfig { - private final ConfigDefault licenseDefault = new ConfigDefault<>("", - "license", AntiVPN.getInstance()), kickStringDefault = - new ConfigDefault<>("Proxies are not allowed on our server", - "kickMessage", AntiVPN.getInstance()), - defaultDatabaseType = new ConfigDefault<>("H2", - "database.type", AntiVPN.getInstance()), - defaultDatabaseName = new ConfigDefault<>("kaurivpn", - "database.database", AntiVPN.getInstance()), - defaultMongoURL = new ConfigDefault<>("", "database.mongoURL", AntiVPN.getInstance()), - defaultUsername = new ConfigDefault<>("root", - "database.username", AntiVPN.getInstance()), - defaultPassword = new ConfigDefault<>("password", - "database.password", AntiVPN.getInstance()), - defaultCountryKickReason = new ConfigDefault<>( - "&cSorry, but our server does not allow connections from\n&f%country%", - "countries.vanillaKickReason", AntiVPN.getInstance()), - defaultIp = new ConfigDefault<>("localhost", "database.ip", AntiVPN.getInstance()), - defaultAlertMsg = new ConfigDefault<>("&8[&6KauriVPN&8] &e%player% &7has joined on a VPN/proxy" + - " &8(&f%reason%&8) &7in location &8(&f%city%&7, &f%country%&8)", "alerts.message", - AntiVPN.getInstance()); - private final ConfigDefault cacheResultsDefault = new ConfigDefault<>(true, - "cachedResults", AntiVPN.getInstance()), - defaultUseCredentials = new ConfigDefault<>(true, - "database.useCredentials", AntiVPN.getInstance()), - defaultDatabaseEnabled = new ConfigDefault<>(false, "database.enabled", - AntiVPN.getInstance()), defaultCommandsEnable = new ConfigDefault<>(false, - "commands.enabled", AntiVPN.getInstance()), defaultKickPlayers - = new ConfigDefault<>(true, "kickPlayers", AntiVPN.getInstance()), - defaultAlertToStaff = new ConfigDefault<>(true, "alerts.enabled", - AntiVPN.getInstance()), - defaultWhitelistCountries = new ConfigDefault<>(true, "countries.whitelist", - AntiVPN.getInstance()), - defaultMetrics = new ConfigDefault<>(true, "bstats", AntiVPN.getInstance()); - private final ConfigDefault - defaultPort = new ConfigDefault<>(-1, "database.port", AntiVPN.getInstance()); - private final ConfigDefault> prefixWhitelistsDefault = new ConfigDefault<>(new ArrayList<>(), - "prefixWhitelists", AntiVPN.getInstance()), defaultCommands = new ConfigDefault<>( - Collections.singletonList("kick %player% VPNs are not allowed on our server!"), "commands.execute", - AntiVPN.getInstance()), - defCountryKickCommands = new ConfigDefault<>(Collections.emptyList(), - "countries.commands", AntiVPN.getInstance()), - defCountrylist = new ConfigDefault<>(new ArrayList<>(), "countries.list", - AntiVPN.getInstance()); + private final ConfigDefault + licenseDefault = new ConfigDefault<>("", "license", AntiVPN.getInstance()), + kickStringDefault = + new ConfigDefault<>( + "Proxies are not allowed on our server", "kickMessage", AntiVPN.getInstance()), + defaultDatabaseType = new ConfigDefault<>("H2", "database.type", AntiVPN.getInstance()), + defaultDatabaseName = + new ConfigDefault<>("kaurivpn", "database.database", AntiVPN.getInstance()), + defaultMongoURL = new ConfigDefault<>("", "database.mongoURL", AntiVPN.getInstance()), + defaultUsername = new ConfigDefault<>("root", "database.username", AntiVPN.getInstance()), + defaultPassword = new ConfigDefault<>("password", "database.password", AntiVPN.getInstance()), + defaultCountryKickReason = + new ConfigDefault<>( + "&cSorry, but our server does not allow connections from\n&f%country%", + "countries.vanillaKickReason", AntiVPN.getInstance()), + defaultIp = new ConfigDefault<>("localhost", "database.ip", AntiVPN.getInstance()), + defaultAlertMsg = + new ConfigDefault<>( + "&8[&6KauriVPN&8] &e%player% &7has joined on a VPN/proxy" + + " &8(&f%reason%&8) &7in location &8(&f%city%&7, &f%country%&8)", + "alerts.message", AntiVPN.getInstance()); + private final ConfigDefault + cacheResultsDefault = new ConfigDefault<>(true, "cachedResults", AntiVPN.getInstance()), + defaultUseCredentials = + new ConfigDefault<>(true, "database.useCredentials", AntiVPN.getInstance()), + defaultDatabaseEnabled = + new ConfigDefault<>(false, "database.enabled", AntiVPN.getInstance()), + defaultCommandsEnable = new ConfigDefault<>(false, "commands.enabled", AntiVPN.getInstance()), + defaultKickPlayers = new ConfigDefault<>(true, "kickPlayers", AntiVPN.getInstance()), + defaultAlertToStaff = new ConfigDefault<>(true, "alerts.enabled", AntiVPN.getInstance()), + defaultWhitelistCountries = + new ConfigDefault<>(true, "countries.whitelist", AntiVPN.getInstance()), + defaultMetrics = new ConfigDefault<>(true, "bstats", AntiVPN.getInstance()); + private final ConfigDefault defaultPort = + new ConfigDefault<>(-1, "database.port", AntiVPN.getInstance()); + private final ConfigDefault> + prefixWhitelistsDefault = + new ConfigDefault<>(new ArrayList<>(), "prefixWhitelists", AntiVPN.getInstance()), + defaultCommands = + new ConfigDefault<>( + Collections.singletonList("kick %player% VPNs are not allowed on our server!"), + "commands.execute", + AntiVPN.getInstance()), + defCountryKickCommands = + new ConfigDefault<>(Collections.emptyList(), "countries.commands", AntiVPN.getInstance()), + defCountrylist = + new ConfigDefault<>(new ArrayList<>(), "countries.list", AntiVPN.getInstance()); - @Getter - private String license; - @Getter - private String kickMessage; - @Getter - private String databaseType; - @Getter - private String databaseName; - private String mongoURL; - @Getter - private String username; - @Getter - private String password; - @Getter - private String ip; - @Getter - private String alertMsg; - @Getter - private String countryVanillaKickReason; - @Getter - private List prefixWhitelists; - private List commands; - @Getter - private List countryList; - private List countryKickCommands; - private int port; - private boolean cacheResults; - @Getter - private boolean databaseEnabled; - private boolean useCredentials; - @Getter - private boolean commandsEnabled; - @Getter - private boolean kickPlayers; - private boolean alertToStaff; - private boolean metrics; - private boolean whitelistCountries; + @Getter private String license; + @Getter private String kickMessage; + @Getter private String databaseType; + @Getter private String databaseName; + private String mongoURL; + @Getter private String username; + @Getter private String password; + @Getter private String ip; + @Getter private String alertMsg; + @Getter private String countryVanillaKickReason; + @Getter private List prefixWhitelists; + private List commands; + @Getter private List countryList; + private List countryKickCommands; + private int port; + private boolean cacheResults; + @Getter private boolean databaseEnabled; + private boolean useCredentials; + @Getter private boolean commandsEnabled; + @Getter private boolean kickPlayers; + private boolean alertToStaff; + private boolean metrics; + private boolean whitelistCountries; - /** - * If true, results will be cached to reduce queries to ... - * @return boolean - */ - public boolean cachedResults() { - return cacheResults; + /** + * If true, results will be cached to reduce queries to ... + * + * @return boolean + */ + public boolean cachedResults() { + return cacheResults; + } + + /** + * If true, staff will be alerted on proxy detection. + * + * @return boolean + */ + public boolean isAlertToSTaff() { + return alertToStaff; + } + + /** + * Commands to run on proxy detection. + * + * @return List + */ + public List commands() { + return commands; + } + + /** + * Whether or not the database we want to connect to requires credentials. + * + * @return boolean + */ + public boolean useDatabaseCreds() { + return useCredentials; + } + + /** + * Only for Mongo only. URL used for connecting to database. Overrides other fields + * + * @return String + */ + public String mongoDatabaseURL() { + return mongoURL; + } + + /** + * If true, we only allow the {@link VPNConfig#countryKickCommands()}. If false, we blacklist + * them. + * + * @return boolean + */ + public boolean getWhitelistCountries() { + return whitelistCountries; + } + + /** + * Returns our configured commands to run on player country detection. + * + * @return List + */ + public List countryKickCommands() { + return countryKickCommands; + } + + /** + * Gets the port based on configuration. If {@link VPNConfig#port} is -1, will get default port + * based on {@link VPNConfig#getDatabaseType()} lowerCase(). + * + * @return int + */ + public int getPort() { + if (port == -1) { + switch (getDatabaseType().toLowerCase()) { + case "mongodb": + case "mongo": + case "mongod": + return 27017; + case "sql": + case "mysql": + return 3306; + } } - /** - * If true, staff will be alerted on proxy detection. - * @return boolean - */ - public boolean isAlertToSTaff() { - return alertToStaff; - } + return port; + } - /** - * Commands to run on proxy detection. - * @return List - */ - public List commands() { - return commands; - } + /** + * If true, ... metrics will be collected to improve KauriVPN. + * + * @return boolean + */ + public boolean metrics() { + return metrics; + } - /** - * Whether or not the database we want to connect to requires credentials. - * @return boolean - */ - public boolean useDatabaseCreds() { - return useCredentials; - } - - /** - * Only for Mongo only. URL used for connecting to database. Overrides other fields - * @return String - */ - public String mongoDatabaseURL() { - return mongoURL; - } - - /** - * If true, we only allow the {@link VPNConfig#countryKickCommands()}. If false, we blacklist them. - * @return boolean - */ - public boolean getWhitelistCountries() { - return whitelistCountries; - } - - /** - * Returns our configured commands to run on player country detection. - * @return List - */ - public List countryKickCommands() { - return countryKickCommands; - } - - /** - * Gets the port based on configuration. If {@link VPNConfig#port} is -1, will get default port - * based on {@link VPNConfig#getDatabaseType()} lowerCase(). - * @return int - */ - public int getPort() { - if(port == -1) { - switch (getDatabaseType().toLowerCase()) { - case "mongodb": - case "mongo": - case "mongod": - return 27017; - case "sql": - case "mysql": - return 3306; - } - } - - return port; - } - - - /** - * If true, ... metrics will be collected to improve KauriVPN. - * @return boolean - */ - public boolean metrics() { - return metrics; - } - - /** - * Grabs all information from the config.yml - */ - public void update() { - license = licenseDefault.get(); - kickMessage = kickStringDefault.get(); - cacheResults = cacheResultsDefault.get(); - prefixWhitelists = prefixWhitelistsDefault.get(); - databaseEnabled = defaultDatabaseEnabled.get(); - useCredentials = defaultUseCredentials.get(); - databaseType = defaultDatabaseType.get(); - databaseName = defaultDatabaseName.get(); - mongoURL = defaultMongoURL.get(); - username = defaultUsername.get(); - password = defaultPassword.get(); - ip = defaultIp.get(); - port = defaultPort.get(); - commandsEnabled = defaultCommandsEnable.get(); - commands = defaultCommands.get(); - kickPlayers = defaultKickPlayers.get(); - alertToStaff = defaultAlertToStaff.get(); - alertMsg = defaultAlertMsg.get(); - metrics = defaultMetrics.get(); - countryList = defCountrylist.get(); - whitelistCountries = defaultWhitelistCountries.get(); - countryKickCommands = defCountryKickCommands.get(); - countryVanillaKickReason = defaultCountryKickReason.get(); - } - -} \ No newline at end of file + /** Grabs all information from the config.yml */ + public void update() { + license = licenseDefault.get(); + kickMessage = kickStringDefault.get(); + cacheResults = cacheResultsDefault.get(); + prefixWhitelists = prefixWhitelistsDefault.get(); + databaseEnabled = defaultDatabaseEnabled.get(); + useCredentials = defaultUseCredentials.get(); + databaseType = defaultDatabaseType.get(); + databaseName = defaultDatabaseName.get(); + mongoURL = defaultMongoURL.get(); + username = defaultUsername.get(); + password = defaultPassword.get(); + ip = defaultIp.get(); + port = defaultPort.get(); + commandsEnabled = defaultCommandsEnable.get(); + commands = defaultCommands.get(); + kickPlayers = defaultKickPlayers.get(); + alertToStaff = defaultAlertToStaff.get(); + alertMsg = defaultAlertMsg.get(); + metrics = defaultMetrics.get(); + countryList = defCountrylist.get(); + whitelistCountries = defaultWhitelistCountries.get(); + countryKickCommands = defCountryKickCommands.get(); + countryVanillaKickReason = defaultCountryKickReason.get(); + } +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/api/VPNExecutor.java b/Common/Source/src/main/java/dev/brighten/antivpn/api/VPNExecutor.java index 12a8137..f1d0ba1 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/api/VPNExecutor.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/api/VPNExecutor.java @@ -25,166 +25,193 @@ import dev.brighten.antivpn.utils.Tuple; import dev.brighten.antivpn.utils.json.JSONException; import dev.brighten.antivpn.web.FunkemunkyAPI; import dev.brighten.antivpn.web.objects.VPNResponse; -import lombok.Getter; - import java.io.IOException; import java.net.UnknownHostException; import java.util.*; import java.util.concurrent.*; import java.util.logging.Level; +import lombok.Getter; @Getter public abstract class VPNExecutor { - private final ScheduledExecutorService threadExecutor = Executors.newScheduledThreadPool(2); - private final Set whitelisted = Collections.synchronizedSet(new HashSet<>()); - private final Set whitelistedIps = Collections.synchronizedSet(new HashSet<>()); - private final Queue> toKick = new LinkedBlockingQueue<>(); - private final Queue playersToRecheck = new LinkedBlockingQueue<>(); - private ScheduledFuture kickTask = null; + private final ScheduledExecutorService threadExecutor = Executors.newScheduledThreadPool(2); + private final Set whitelisted = Collections.synchronizedSet(new HashSet<>()); + private final Set whitelistedIps = Collections.synchronizedSet(new HashSet<>()); + private final Queue> toKick = new LinkedBlockingQueue<>(); + private final Queue playersToRecheck = new LinkedBlockingQueue<>(); + private ScheduledFuture kickTask = null; + public abstract void registerListeners(); - public abstract void registerListeners(); + public abstract void log(Level level, String log, Object... objects); - public abstract void log(Level level, String log, Object... objects); + public abstract void log(String log, Object... objects); - public abstract void log(String log, Object... objects); + public abstract void logException(String message, Throwable ex); - public abstract void logException(String message, Throwable ex); + public abstract void runCommand(String command); - public abstract void runCommand(String command); + public void logException(Throwable ex) { + logException("An exception occurred: " + ex.getMessage(), ex); + } - public void logException(Throwable ex) { - logException("An exception occurred: " + ex.getMessage(), ex); - } - - public void startKickChecks() { - kickTask = threadExecutor.scheduleAtFixedRate(() -> { - synchronized (toKick) { - if(toKick.isEmpty()) return; + public void startKickChecks() { + kickTask = + threadExecutor.scheduleAtFixedRate( + () -> { + synchronized (toKick) { + if (toKick.isEmpty()) return; Tuple toCheck; - while((toCheck = toKick.poll()) != null) { - Optional player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(toCheck.second()); + while ((toCheck = toKick.poll()) != null) { + Optional player = + AntiVPN.getInstance().getPlayerExecutor().getPlayer(toCheck.second()); - if(player.isEmpty()) { - continue; - } + if (player.isEmpty()) { + continue; + } - handleKickingOfPlayer(toCheck.first(), player.get()); + handleKickingOfPlayer(toCheck.first(), player.get()); } - } - }, 8, 2, TimeUnit.SECONDS); + } + }, + 8, + 2, + TimeUnit.SECONDS); + } + + public void handleKickingOfPlayer(CheckResult result, APIPlayer player) { + + // Ensuring kick task is always running + if (kickTask == null || kickTask.isDone() || kickTask.isCancelled()) { + startKickChecks(); } - public void handleKickingOfPlayer(CheckResult result, APIPlayer player) { + if (AntiVPN.getInstance().getVpnConfig().isAlertToSTaff()) + AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream() + .filter(APIPlayer::isAlertsEnabled) + .forEach( + pl -> + pl.sendMessage( + StringUtil.translateAlternateColorCodes( + '&', + StringUtil.varReplace( + dev.brighten.antivpn.AntiVPN.getInstance() + .getVpnConfig() + .getAlertMsg(), + player, + result.response())))); - //Ensuring kick task is always running - if(kickTask == null || kickTask.isDone() || kickTask.isCancelled()) { - startKickChecks(); - } + if (AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { + switch (result.resultType()) { + case DENIED_PROXY -> + player.kickPlayer( + StringUtil.varReplace( + AntiVPN.getInstance().getVpnConfig().getKickMessage(), + player, + result.response())); + case DENIED_COUNTRY -> + player.kickPlayer( + StringUtil.varReplace( + AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(), + player, + result.response())); + } + } else { + if (!AntiVPN.getInstance().getVpnConfig().isCommandsEnabled()) return; + } - if (AntiVPN.getInstance().getVpnConfig().isAlertToSTaff()) AntiVPN.getInstance().getPlayerExecutor() - .getOnlinePlayers() - .stream() - .filter(APIPlayer::isAlertsEnabled) - .forEach(pl -> - pl.sendMessage(StringUtil.translateAlternateColorCodes('&', - StringUtil.varReplace(dev.brighten.antivpn.AntiVPN.getInstance().getVpnConfig() - .getAlertMsg(), player, result.response())))); - - if(AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { - switch (result.resultType()) { - case DENIED_PROXY -> player.kickPlayer(StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() - .getKickMessage(), player, result.response())); - case DENIED_COUNTRY -> player.kickPlayer(StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() - .getCountryVanillaKickReason(), player, result.response())); + Runnable runCommands = + () -> { + switch (result.resultType()) { + case DENIED_PROXY -> { + for (String command : AntiVPN.getInstance().getVpnConfig().commands()) { + runCommand(StringUtil.varReplace(command, player, result.response())); + } } - } else { - if(!AntiVPN.getInstance().getVpnConfig().isCommandsEnabled()) return; - } - - Runnable runCommands = () -> { - switch (result.resultType()) { - case DENIED_PROXY -> { - for (String command : AntiVPN.getInstance().getVpnConfig().commands()) { - runCommand(StringUtil.varReplace(command, player, result.response())); - } - } - case DENIED_COUNTRY -> { - for (String command : AntiVPN.getInstance().getVpnConfig().countryKickCommands()) { - runCommand(StringUtil.varReplace(command, player, result.response())); - } - } + case DENIED_COUNTRY -> { + for (String command : AntiVPN.getInstance().getVpnConfig().countryKickCommands()) { + runCommand(StringUtil.varReplace(command, player, result.response())); + } } + } }; - // Fixes the commands running too fast and causing messaging errors by any downstream plugins like LiteBans - var scheduleResult = threadExecutor.schedule(runCommands, 1, TimeUnit.SECONDS); + // Fixes the commands running too fast and causing messaging errors by any downstream plugins + // like LiteBans + var scheduleResult = threadExecutor.schedule(runCommands, 200, TimeUnit.MILLISECONDS); - if(scheduleResult.isCancelled()) { - runCommands.run(); - } - - //Ensuring players are actually kicked as they are supposed to be. - toKick.add(new Tuple<>(result, player.getUuid())); + if (scheduleResult.isCancelled()) { + runCommands.run(); } - public boolean isWhitelisted(UUID uuid) { - if(AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) { - return AntiVPN.getInstance().getDatabase().isWhitelisted(uuid); - } - return whitelisted.contains(uuid); + var toAdd = new Tuple<>(result, player.getUuid()); + // Ensuring players are actually kicked as they are supposed to be. + threadExecutor.schedule( + () -> { + toKick.add(toAdd); + }, + 500, + TimeUnit.MILLISECONDS); + } + + public boolean isWhitelisted(UUID uuid) { + if (AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) { + return AntiVPN.getInstance().getDatabase().isWhitelisted(uuid); + } + return whitelisted.contains(uuid); + } + + public boolean isWhitelisted(String cidr) { + if (AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) { + return AntiVPN.getInstance().getDatabase().isWhitelisted(cidr); + } + try { + return whitelistedIps.contains(new CIDRUtils(cidr)); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + + private final Cache cachedResponses = + Caffeine.newBuilder().expireAfterWrite(20, TimeUnit.MINUTES).maximumSize(4000).build(); + + public CompletableFuture checkIp(String ip) { + VPNResponse cached = cachedResponses.getIfPresent(ip); + + if (cached != null) { + return CompletableFuture.completedFuture(cached); } - public boolean isWhitelisted(String cidr) { - if(AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) { - return AntiVPN.getInstance().getDatabase().isWhitelisted(cidr); - } - try { - return whitelistedIps.contains(new CIDRUtils(cidr)); - } catch (UnknownHostException e) { - throw new RuntimeException(e); - } - } + return CompletableFuture.supplyAsync( + () -> { + Optional cachedRes = + AntiVPN.getInstance().getDatabase().getStoredResponse(ip); - private final Cache cachedResponses = Caffeine.newBuilder() - .expireAfterWrite(20, TimeUnit.MINUTES) - .maximumSize(4000) - .build(); + if (cachedRes.isPresent()) { + return cachedRes.get(); + } else { + try { + VPNResponse response = + FunkemunkyAPI.getVPNResponse( + ip, AntiVPN.getInstance().getVpnConfig().getLicense(), true); - public CompletableFuture checkIp(String ip) { - VPNResponse cached = cachedResponses.getIfPresent(ip); + if (response.isSuccess()) { + AntiVPN.getInstance().getDatabase().cacheResponse(response); + } else { + log("Query to VPN API failed! Reason: " + response.getFailureReason()); + } - if(cached != null) { - return CompletableFuture.completedFuture(cached); - } - - return CompletableFuture.supplyAsync(() -> { - Optional cachedRes = AntiVPN.getInstance().getDatabase().getStoredResponse(ip); - - if(cachedRes.isPresent()) { - return cachedRes.get(); + return response; + } catch (JSONException | IOException e) { + log("Query to VPN API failed! Reason: " + e.getMessage()); + return VPNResponse.FAILED_RESPONSE; } - else { - try { - VPNResponse response = FunkemunkyAPI - .getVPNResponse(ip, AntiVPN.getInstance().getVpnConfig().getLicense(), true); + } + }, + threadExecutor); + } - if (response.isSuccess()) { - AntiVPN.getInstance().getDatabase().cacheResponse(response); - } else { - log("Query to VPN API failed! Reason: " + response.getFailureReason()); - } - - return response; - } catch (JSONException | IOException e) { - log("Query to VPN API failed! Reason: " + e.getMessage()); - return VPNResponse.FAILED_RESPONSE; - } - } - }, threadExecutor); - } - - public abstract void disablePlugin(); + public abstract void disablePlugin(); } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/command/Command.java b/Common/Source/src/main/java/dev/brighten/antivpn/command/Command.java index 43baa55..ead829b 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/command/Command.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/command/Command.java @@ -20,21 +20,21 @@ import java.util.List; public abstract class Command { - public abstract String permission(); + public abstract String permission(); - public abstract String name(); + public abstract String name(); - public abstract String[] aliases(); + public abstract String[] aliases(); - public abstract String description(); + public abstract String description(); - public abstract String usage(); + public abstract String usage(); - public abstract String parent(); + public abstract String parent(); - public abstract Command[] children(); - - public abstract String execute(CommandExecutor executor, String[] args); + public abstract Command[] children(); - public abstract List tabComplete(CommandExecutor executor, String alias, String[] args); + public abstract String execute(CommandExecutor executor, String[] args); + + public abstract List tabComplete(CommandExecutor executor, String alias, String[] args); } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/command/CommandExecutor.java b/Common/Source/src/main/java/dev/brighten/antivpn/command/CommandExecutor.java index d781498..cc21fb1 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/command/CommandExecutor.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/command/CommandExecutor.java @@ -17,14 +17,15 @@ package dev.brighten.antivpn.command; import dev.brighten.antivpn.api.APIPlayer; - import java.util.Optional; public interface CommandExecutor { - void sendMessage(String message, Object... objects); - boolean hasPermission(String permission); - Optional getPlayer(); - boolean isPlayer(); + void sendMessage(String message, Object... objects); + boolean hasPermission(String permission); + + Optional getPlayer(); + + boolean isPlayer(); } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/AlertsCommand.java b/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/AlertsCommand.java index 445f479..be8afa2 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/AlertsCommand.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/AlertsCommand.java @@ -21,64 +21,68 @@ import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.message.VpnString; - import java.util.Collections; import java.util.List; import java.util.Optional; public class AlertsCommand extends Command { - @Override - public String permission() { - return "antivpn.command.alerts"; - } + @Override + public String permission() { + return "antivpn.command.alerts"; + } - @Override - public String name() { - return "alerts"; - } + @Override + public String name() { + return "alerts"; + } - @Override - public String[] aliases() { - return new String[] {"valerts", "vpnalerts"}; - } + @Override + public String[] aliases() { + return new String[] {"valerts", "vpnalerts"}; + } - @Override - public String description() { - return "toggle VPN use alerts"; - } + @Override + public String description() { + return "toggle VPN use alerts"; + } - @Override - public String usage() { - return ""; - } + @Override + public String usage() { + return ""; + } - @Override - public String parent() { - return "antivpn"; - } + @Override + public String parent() { + return "antivpn"; + } - @Override - public Command[] children() { - return new Command[0]; - } + @Override + public Command[] children() { + return new Command[0]; + } - @Override - public String execute(CommandExecutor executor, String[] args) { - Optional pgetter = executor.getPlayer(); - if(!pgetter.isPresent()) return AntiVPN.getInstance().getMessageHandler() - .getString("command-misc-playerRequired").getMessage(); + @Override + public String execute(CommandExecutor executor, String[] args) { + Optional pgetter = executor.getPlayer(); + if (!pgetter.isPresent()) + return AntiVPN.getInstance() + .getMessageHandler() + .getString("command-misc-playerRequired") + .getMessage(); - APIPlayer player = pgetter.get(); + APIPlayer player = pgetter.get(); - player.setAlertsEnabled(!player.isAlertsEnabled()); - player.updateAlertsState(); + player.setAlertsEnabled(!player.isAlertsEnabled()); + player.updateAlertsState(); - return AntiVPN.getInstance().getMessageHandler().getString("command-alerts-toggled") - .getFormattedMessage(new VpnString.Var<>("state", player.isAlertsEnabled())); - } + return AntiVPN.getInstance() + .getMessageHandler() + .getString("command-alerts-toggled") + .getFormattedMessage(new VpnString.Var<>("state", player.isAlertsEnabled())); + } - @Override - public List tabComplete(CommandExecutor executor, String alias, String[] args) { - return Collections.emptyList(); - } + @Override + public List tabComplete(CommandExecutor executor, String alias, String[] args) { + return Collections.emptyList(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/AllowlistCommand.java b/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/AllowlistCommand.java index 5ad7e7c..2a3f31a 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/AllowlistCommand.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/AllowlistCommand.java @@ -22,315 +22,349 @@ import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.utils.MiscUtils; - import java.net.UnknownHostException; import java.util.*; import java.util.stream.Collectors; public class AllowlistCommand extends Command { - private static final String[] secondArgs = new String[] {"add", "remove", "show", "search"}; + private static final String[] secondArgs = new String[] {"add", "remove", "show", "search"}; - @Override - public String permission() { - return "antivpn.command.allowlist"; + @Override + public String permission() { + return "antivpn.command.allowlist"; + } + + @Override + public String name() { + return "allowlist"; + } + + @Override + public String[] aliases() { + return new String[] {"whitelist"}; + } + + @Override + public String description() { + return "Add/remove players to/from exemption list."; + } + + @Override + public String usage() { + return " | remove | show [page] | search [page]>"; + } + + @Override + public String parent() { + return "antivpn"; + } + + @Override + public Command[] children() { + return new Command[0]; + } + + @Override + public String execute(CommandExecutor executor, String[] args) { + if (args.length == 0 + || Arrays.stream(secondArgs).noneMatch(arg -> arg.equalsIgnoreCase(args[0]))) { + return "&cUsage: /antivpn allowlist " + usage(); } - @Override - public String name() { - return "allowlist"; - } - - @Override - public String[] aliases() { - return new String[] {"whitelist"}; - } - - @Override - public String description() { - return "Add/remove players to/from exemption list."; - } - - @Override - public String usage() { - return " | remove | show [page] | search [page]>"; - } - - @Override - public String parent() { - return "antivpn"; - } - - @Override - public Command[] children() { - return new Command[0]; - } - - @Override - public String execute(CommandExecutor executor, String[] args) { - if(args.length == 0 || Arrays.stream(secondArgs).noneMatch(arg -> arg.equalsIgnoreCase(args[0]))) { - return "&cUsage: /antivpn allowlist " + usage(); - } - - if(args[0].equalsIgnoreCase("show")) { - // args[1] = optional page number (defaults to 1) - int page = 1; - if (args.length > 1) { - try { - page = Integer.parseInt(args[1]); - if (page < 1) page = 1; - } catch (NumberFormatException e) { - return "&cUsage: /antivpn allowlist show [page]"; - } - } - - boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled(); - - List uuids = databaseEnabled - ? AntiVPN.getInstance().getDatabase().getAllWhitelisted() - : new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelisted()); - List ips = databaseEnabled - ? AntiVPN.getInstance().getDatabase().getAllWhitelistedIps() - : new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelistedIps()); - - List entries = new ArrayList<>(); - for (UUID uuid : uuids) { - entries.add("&7- &fUUID: &e" + uuid); - } - for (CIDRUtils cidr : ips) { - entries.add("&7- &fIP: &e" + cidr.getCidr()); - } - - return buildPage(entries, page, null, "show"); - } - - if(args[0].equalsIgnoreCase("search")) { - // args[1..n-1] = query terms; args[n] = optional page number if last arg is an integer - if (args.length < 2) { - return "&cUsage: /antivpn allowlist search [page]"; - } - - // Detect optional trailing page number - int page = 1; - int queryEnd = args.length; - try { - int candidate = Integer.parseInt(args[args.length - 1]); - if (candidate >= 1 && args.length > 2) { - page = candidate; - queryEnd = args.length - 1; - } - } catch (NumberFormatException ignored) {} - - String search = String.join(" ", Arrays.copyOfRange(args, 1, queryEnd)).toLowerCase(); - // Strip color code characters to prevent formatting injection in output - String safeSearch = search.replace("&", ""); - - if (safeSearch.isEmpty()) { - return "&cUsage: /antivpn allowlist search [page]"; - } - - boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled(); - - List uuids = databaseEnabled - ? AntiVPN.getInstance().getDatabase().getAllWhitelisted() - : new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelisted()); - List ips = databaseEnabled - ? AntiVPN.getInstance().getDatabase().getAllWhitelistedIps() - : new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelistedIps()); - - List entries = new ArrayList<>(); - for (UUID uuid : uuids) { - String entry = uuid.toString(); - if (entry.toLowerCase().contains(search)) { - entries.add("&7- &fUUID: &e" + entry); - } - } - for (CIDRUtils cidr : ips) { - String entry = cidr.getCidr(); - if (entry.toLowerCase().contains(search)) { - entries.add("&7- &fIP: &e" + entry); - } - } - - return buildPage(entries, page, safeSearch, "search " + safeSearch); - } - - if(args.length == 1) - return "&cYou have to provide a player to allow or deny exemption."; - - boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled(); - - if(!databaseEnabled) executor.sendMessage("&cThe database is currently not setup, " + - "so any changes here will disappear after a restart."); - - CIDRUtils cidrUtils; - + if (args[0].equalsIgnoreCase("show")) { + // args[1] = optional page number (defaults to 1) + int page = 1; + if (args.length > 1) { try { - cidrUtils = new CIDRUtils(args[1]); - } catch(IllegalArgumentException | UnknownHostException e) { - cidrUtils = null; + page = Integer.parseInt(args[1]); + if (page < 1) page = 1; + } catch (NumberFormatException e) { + return "&cUsage: /antivpn allowlist show [page]"; } + } - if(cidrUtils != null) { - if(!databaseEnabled) { - return switch (args[0].toLowerCase()) { - case "add", "insert" -> { - AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(cidrUtils); - yield String.format("&aAdded &6%s &ato exemption allowlist.", cidrUtils.getCidr()); - } - case "remove", "delete" -> { - AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(cidrUtils); - yield String.format("&cRemoved &%s &cfrom the exemption allowlist.", cidrUtils.getCidr()); - } - default -> "&c\"" + args[0] + "\" is not a valid argument"; - }; - } else return switch (args[0].toLowerCase()) { - case "add", "insert" -> { - AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(cidrUtils); - AntiVPN.getInstance().getDatabase().addWhitelist(cidrUtils); - yield String.format("&aAdded &6%s &ato exemption allowlist.", cidrUtils.getCidr()); - } - case "remove", "delete" -> { - AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(cidrUtils); - AntiVPN.getInstance().getDatabase().removeWhitelist(cidrUtils); - yield String.format("&cRemoved &6%s &cfrom the exemption allowlist.", cidrUtils.getCidr()); - } - default -> "&c\"" + args[0] + "\" is not a valid argument"; - }; - } - if(MiscUtils.isIpv4(args[1])) { - if(!databaseEnabled) { - try { - return switch(args[0].toLowerCase()) { - case "add", "insert" -> { - AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(new CIDRUtils(args[1] + "/32")); - AntiVPN.getInstance().getDatabase().addWhitelist(new CIDRUtils(args[1] + "/32")); - yield String.format("&aAdded &6%s &ato the exemption allowlist.", args[1] + "/32"); - } - case "remove", "delete" -> { - AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(new CIDRUtils(args[1] + "/32")); - AntiVPN.getInstance().getDatabase().removeWhitelist(new CIDRUtils(args[1] + "/32")); - yield String.format("&cRemoved &6%s &cfrom the exemption allowlist.", args[1] + "/32"); - } - default -> "&c\"" + args[0] + "\" is not a valid argument"; - }; - } catch (UnknownHostException e) { - AntiVPN.getInstance().getExecutor().logException("Invalid IP format for allowlist command", e); - return "&cInvalid IP format for allowlist command"; - } - } else { - try { - return switch (args[0].toLowerCase()) { - case "add", "insert" -> { - AntiVPN.getInstance().getDatabase().addWhitelist(new CIDRUtils(args[1] + "/32")); - yield String.format("&aAdded &6%s &a to the exemption allowlist.", args[1] + "/32"); - } - case "remove", "delete" -> { - AntiVPN.getInstance().getDatabase().removeWhitelist(new CIDRUtils(args[1] + "/32")); - yield String.format("&cRemoved &6%s &c from the exemption allowlist.", args[1] + "/32"); - } - default -> "&c\"" + args[0] + "\" is not a valid argument"; - }; - } catch (UnknownHostException e) { - AntiVPN.getInstance().getExecutor().logException("Invalid IP format for allowlist command", e); - return "&cInvalid IP format for allowlist command"; - } - } - } else { - UUID uuid; - try { - uuid = UUID.fromString(args[1]); - } catch(IllegalArgumentException e) { - Optional player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(args[1]); - if (player.isPresent()) { - uuid = player.get().getUuid(); - } else { - uuid = MiscUtils.lookupUUID(args[1]); - if (uuid == null) { - return "&cCould not find a UUID for \"" + args[1] + "\". They might not have provided a valid username."; - } - } - } + boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled(); - if(!databaseEnabled) { - return switch (args[0].toLowerCase()) { - case "add" -> { - AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid); - yield String.format("&aAdded &6%s &auuid to the exemption allowlist.", uuid.toString()); - } - case "remove", "delete" -> { - AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid); - yield String.format("&cRemoved &6%s &cuuid from the exemption allowlist.", uuid.toString()); - } - default -> "&c\"" + args[0] + "\" is not a valid argument"; - }; - } else { - return switch (args[0].toLowerCase()) { - case "add" -> { - AntiVPN.getInstance().getDatabase().addWhitelist(uuid); - yield String.format("&aAdded &6%s &auuid to the exemption allowlist.", uuid.toString()); - } - case "remove", "delete" -> { - AntiVPN.getInstance().getDatabase().removeWhitelist(uuid); - yield String.format("&cRemoved &6%s &cuuid from the exemption allowlist.", uuid.toString()); - } - default -> "&c\"" + args[0] + "\" is not a valid argument"; - }; - } - } + List uuids = + databaseEnabled + ? AntiVPN.getInstance().getDatabase().getAllWhitelisted() + : new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelisted()); + List ips = + databaseEnabled + ? AntiVPN.getInstance().getDatabase().getAllWhitelistedIps() + : new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelistedIps()); + + List entries = new ArrayList<>(); + for (UUID uuid : uuids) { + entries.add("&7- &fUUID: &e" + uuid); + } + for (CIDRUtils cidr : ips) { + entries.add("&7- &fIP: &e" + cidr.getCidr()); + } + + return buildPage(entries, page, null, "show"); } - @Override - public List tabComplete(CommandExecutor executor, String alias, String[] args) { - return switch (args.length) { - case 1 -> Arrays.stream(secondArgs) - .filter(narg -> narg.toLowerCase().startsWith(args[0].toLowerCase())) - .collect(Collectors.toList()); - case 2 -> { - if (args[0].equalsIgnoreCase("show") || args[0].equalsIgnoreCase("search")) { - yield Collections.emptyList(); - } - yield AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream() - .map(APIPlayer::getName) - .filter(name -> name.toLowerCase().startsWith(args[1].toLowerCase())) - .collect(Collectors.toList()); - } - default -> Collections.emptyList(); + if (args[0].equalsIgnoreCase("search")) { + // args[1..n-1] = query terms; args[n] = optional page number if last arg is an integer + if (args.length < 2) { + return "&cUsage: /antivpn allowlist search [page]"; + } + + // Detect optional trailing page number + int page = 1; + int queryEnd = args.length; + try { + int candidate = Integer.parseInt(args[args.length - 1]); + if (candidate >= 1 && args.length > 2) { + page = candidate; + queryEnd = args.length - 1; + } + } catch (NumberFormatException ignored) { + } + + String search = String.join(" ", Arrays.copyOfRange(args, 1, queryEnd)).toLowerCase(); + // Strip color code characters to prevent formatting injection in output + String safeSearch = search.replace("&", ""); + + if (safeSearch.isEmpty()) { + return "&cUsage: /antivpn allowlist search [page]"; + } + + boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled(); + + List uuids = + databaseEnabled + ? AntiVPN.getInstance().getDatabase().getAllWhitelisted() + : new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelisted()); + List ips = + databaseEnabled + ? AntiVPN.getInstance().getDatabase().getAllWhitelistedIps() + : new ArrayList<>(AntiVPN.getInstance().getExecutor().getWhitelistedIps()); + + List entries = new ArrayList<>(); + for (UUID uuid : uuids) { + String entry = uuid.toString(); + if (entry.toLowerCase().contains(search)) { + entries.add("&7- &fUUID: &e" + entry); + } + } + for (CIDRUtils cidr : ips) { + String entry = cidr.getCidr(); + if (entry.toLowerCase().contains(search)) { + entries.add("&7- &fIP: &e" + entry); + } + } + + return buildPage(entries, page, safeSearch, "search " + safeSearch); + } + + if (args.length == 1) return "&cYou have to provide a player to allow or deny exemption."; + + boolean databaseEnabled = AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled(); + + if (!databaseEnabled) + executor.sendMessage( + "&cThe database is currently not setup, " + + "so any changes here will disappear after a restart."); + + CIDRUtils cidrUtils; + + try { + cidrUtils = new CIDRUtils(args[1]); + } catch (IllegalArgumentException | UnknownHostException e) { + cidrUtils = null; + } + + if (cidrUtils != null) { + if (!databaseEnabled) { + return switch (args[0].toLowerCase()) { + case "add", "insert" -> { + AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(cidrUtils); + yield String.format("&aAdded &6%s &ato exemption allowlist.", cidrUtils.getCidr()); + } + case "remove", "delete" -> { + AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(cidrUtils); + yield String.format( + "&cRemoved &%s &cfrom the exemption allowlist.", cidrUtils.getCidr()); + } + default -> "&c\"" + args[0] + "\" is not a valid argument"; + }; + } else + return switch (args[0].toLowerCase()) { + case "add", "insert" -> { + AntiVPN.getInstance().getExecutor().getWhitelistedIps().add(cidrUtils); + AntiVPN.getInstance().getDatabase().addWhitelist(cidrUtils); + yield String.format("&aAdded &6%s &ato exemption allowlist.", cidrUtils.getCidr()); + } + case "remove", "delete" -> { + AntiVPN.getInstance().getExecutor().getWhitelistedIps().remove(cidrUtils); + AntiVPN.getInstance().getDatabase().removeWhitelist(cidrUtils); + yield String.format( + "&cRemoved &6%s &cfrom the exemption allowlist.", cidrUtils.getCidr()); + } + default -> "&c\"" + args[0] + "\" is not a valid argument"; }; } - - private String buildPage(List entries, int page, String safeSearch, String subcommandPrefix) { - int pageSize = 10; - int totalPages = Math.max(1, (entries.size() + pageSize - 1) / pageSize); - if (page > totalPages) page = totalPages; - - List messages = new ArrayList<>(); - messages.add("&8&m-----------------------------------------------------"); - messages.add("&6&lAllowlist Entries &8(&7Page &f" + page + "&7/&f" + totalPages + "&8)" - + (safeSearch != null ? " &7(search: &f" + safeSearch + "&7)" : "")); - messages.add(""); - - if (entries.isEmpty()) { - messages.add(safeSearch != null - ? "&cNo allowlist entries matching &f\"" + safeSearch + "&c\" were found." - : "&cThe allowlist is empty."); - } else { - int start = (page - 1) * pageSize; - int end = Math.min(start + pageSize, entries.size()); - for (int i = start; i < end; i++) { - messages.add(entries.get(i)); + if (MiscUtils.isIpv4(args[1])) { + if (!databaseEnabled) { + try { + return switch (args[0].toLowerCase()) { + case "add", "insert" -> { + AntiVPN.getInstance() + .getExecutor() + .getWhitelistedIps() + .add(new CIDRUtils(args[1] + "/32")); + AntiVPN.getInstance().getDatabase().addWhitelist(new CIDRUtils(args[1] + "/32")); + yield String.format("&aAdded &6%s &ato the exemption allowlist.", args[1] + "/32"); } - if (totalPages > 1) { - messages.add(""); - if (page > 1) { - messages.add("&7Previous page: &f/antivpn allowlist " + subcommandPrefix + " " + (page - 1)); - } - if (page < totalPages) { - messages.add("&7Next page: &f/antivpn allowlist " + subcommandPrefix + " " + (page + 1)); - } + case "remove", "delete" -> { + AntiVPN.getInstance() + .getExecutor() + .getWhitelistedIps() + .remove(new CIDRUtils(args[1] + "/32")); + AntiVPN.getInstance().getDatabase().removeWhitelist(new CIDRUtils(args[1] + "/32")); + yield String.format( + "&cRemoved &6%s &cfrom the exemption allowlist.", args[1] + "/32"); } + default -> "&c\"" + args[0] + "\" is not a valid argument"; + }; + } catch (UnknownHostException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Invalid IP format for allowlist command", e); + return "&cInvalid IP format for allowlist command"; } - messages.add("&8&m-----------------------------------------------------"); - return String.join("\n", messages); + } else { + try { + return switch (args[0].toLowerCase()) { + case "add", "insert" -> { + AntiVPN.getInstance().getDatabase().addWhitelist(new CIDRUtils(args[1] + "/32")); + yield String.format("&aAdded &6%s &a to the exemption allowlist.", args[1] + "/32"); + } + case "remove", "delete" -> { + AntiVPN.getInstance().getDatabase().removeWhitelist(new CIDRUtils(args[1] + "/32")); + yield String.format( + "&cRemoved &6%s &c from the exemption allowlist.", args[1] + "/32"); + } + default -> "&c\"" + args[0] + "\" is not a valid argument"; + }; + } catch (UnknownHostException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Invalid IP format for allowlist command", e); + return "&cInvalid IP format for allowlist command"; + } + } + } else { + UUID uuid; + try { + uuid = UUID.fromString(args[1]); + } catch (IllegalArgumentException e) { + Optional player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(args[1]); + if (player.isPresent()) { + uuid = player.get().getUuid(); + } else { + uuid = MiscUtils.lookupUUID(args[1]); + if (uuid == null) { + return "&cCould not find a UUID for \"" + + args[1] + + "\". They might not have provided a valid username."; + } + } + } + + if (!databaseEnabled) { + return switch (args[0].toLowerCase()) { + case "add" -> { + AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid); + yield String.format("&aAdded &6%s &auuid to the exemption allowlist.", uuid.toString()); + } + case "remove", "delete" -> { + AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid); + yield String.format( + "&cRemoved &6%s &cuuid from the exemption allowlist.", uuid.toString()); + } + default -> "&c\"" + args[0] + "\" is not a valid argument"; + }; + } else { + return switch (args[0].toLowerCase()) { + case "add" -> { + AntiVPN.getInstance().getDatabase().addWhitelist(uuid); + yield String.format("&aAdded &6%s &auuid to the exemption allowlist.", uuid.toString()); + } + case "remove", "delete" -> { + AntiVPN.getInstance().getDatabase().removeWhitelist(uuid); + yield String.format( + "&cRemoved &6%s &cuuid from the exemption allowlist.", uuid.toString()); + } + default -> "&c\"" + args[0] + "\" is not a valid argument"; + }; + } } + } + + @Override + public List tabComplete(CommandExecutor executor, String alias, String[] args) { + return switch (args.length) { + case 1 -> + Arrays.stream(secondArgs) + .filter(narg -> narg.toLowerCase().startsWith(args[0].toLowerCase())) + .collect(Collectors.toList()); + case 2 -> { + if (args[0].equalsIgnoreCase("show") || args[0].equalsIgnoreCase("search")) { + yield Collections.emptyList(); + } + yield AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream() + .map(APIPlayer::getName) + .filter(name -> name.toLowerCase().startsWith(args[1].toLowerCase())) + .collect(Collectors.toList()); + } + default -> Collections.emptyList(); + }; + } + + private String buildPage( + List entries, int page, String safeSearch, String subcommandPrefix) { + int pageSize = 10; + int totalPages = Math.max(1, (entries.size() + pageSize - 1) / pageSize); + if (page > totalPages) page = totalPages; + + List messages = new ArrayList<>(); + messages.add("&8&m-----------------------------------------------------"); + messages.add( + "&6&lAllowlist Entries &8(&7Page &f" + + page + + "&7/&f" + + totalPages + + "&8)" + + (safeSearch != null ? " &7(search: &f" + safeSearch + "&7)" : "")); + messages.add(""); + + if (entries.isEmpty()) { + messages.add( + safeSearch != null + ? "&cNo allowlist entries matching &f\"" + safeSearch + "&c\" were found." + : "&cThe allowlist is empty."); + } else { + int start = (page - 1) * pageSize; + int end = Math.min(start + pageSize, entries.size()); + for (int i = start; i < end; i++) { + messages.add(entries.get(i)); + } + if (totalPages > 1) { + messages.add(""); + if (page > 1) { + messages.add( + "&7Previous page: &f/antivpn allowlist " + subcommandPrefix + " " + (page - 1)); + } + if (page < totalPages) { + messages.add("&7Next page: &f/antivpn allowlist " + subcommandPrefix + " " + (page + 1)); + } + } + } + messages.add("&8&m-----------------------------------------------------"); + return String.join("\n", messages); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/AntiVPNCommand.java b/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/AntiVPNCommand.java index 15f23a0..2469565 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/AntiVPNCommand.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/AntiVPNCommand.java @@ -20,7 +20,6 @@ import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.utils.StringUtil; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -28,73 +27,95 @@ import java.util.List; import java.util.stream.Collectors; public class AntiVPNCommand extends Command { - @Override - public String permission() { - return "antivpn.command"; + @Override + public String permission() { + return "antivpn.command"; + } + + @Override + public String name() { + return "antivpn"; + } + + @Override + public String[] aliases() { + return new String[] {"kaurivpn", "kvpn", "vpn", "avpn"}; + } + + @Override + public String description() { + return "The main help command"; + } + + @Override + public String usage() { + return ""; + } + + @Override + public String parent() { + return ""; + } + + @Override + public Command[] children() { + return new Command[] { + new LookupCommand(), + new AllowlistCommand(), + new AlertsCommand(), + new ClearCacheCommand(), + new PlanCommand(), + new ReloadCommand() + }; + } + + @Override + public String execute(CommandExecutor uuid, String[] args) { + List messages = new ArrayList<>(); + + messages.add(StringUtil.line("&8")); + messages.add("&6&lAntiVPN Help Page"); + messages.add(""); + for (Command cmd : AntiVPN.getInstance().getCommands()) { + messages.add( + String.format( + "&8/&f%s &8- &7&o%s", + "&7" + + cmd.parent() + + (cmd.parent().length() > 0 ? " " : "") + + "&f" + + cmd.name() + + " &7" + + cmd.usage(), + cmd.description())); + } + for (Command child : children()) { + messages.add( + String.format( + "&8/&f%s &8- &7&o%s", + "&7" + + child.parent() + + (child.parent().length() > 0 ? " " : "") + + "&f" + + child.name() + + " &7" + + child.usage(), + child.description())); } - @Override - public String name() { - return "antivpn"; - } + messages.add(StringUtil.line("&8")); - @Override - public String[] aliases() { - return new String[] {"kaurivpn", "kvpn", "vpn", "avpn"}; - } + return String.join("\n", messages); + } - @Override - public String description() { - return "The main help command"; - } + @Override + public List tabComplete(CommandExecutor executor, String alias, String[] args) { + if (args.length == 1) + return Arrays.stream(children()) + .map(Command::name) + .filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase())) + .collect(Collectors.toList()); - @Override - public String usage() { - return ""; - } - - @Override - public String parent() { - return ""; - } - - @Override - public Command[] children() { - return new Command[] {new LookupCommand(), new AllowlistCommand(), new AlertsCommand(), - new ClearCacheCommand(), new PlanCommand(), new ReloadCommand()}; - } - - @Override - public String execute(CommandExecutor uuid, String[] args) { - List messages = new ArrayList<>(); - - messages.add(StringUtil.line("&8")); - messages.add("&6&lAntiVPN Help Page"); - messages.add(""); - for (Command cmd : AntiVPN.getInstance().getCommands()) { - messages.add(String.format("&8/&f%s &8- &7&o%s", "&7" + cmd.parent() - + (cmd.parent().length() > 0 ? " " : "") + "&f" + cmd.name() + " &7" - + cmd.usage(), cmd.description())); - } - for (Command child : children()) { - messages.add(String.format("&8/&f%s &8- &7&o%s", "&7" + child.parent() - + (child.parent().length() > 0 ? " " : "") + "&f" + child.name() + " &7" - + child.usage(), child.description())); - } - - messages.add(StringUtil.line("&8")); - - return String.join("\n", messages); - } - - @Override - public List tabComplete(CommandExecutor executor, String alias, String[] args) { - if(args.length == 1) - return Arrays.stream(children()) - .map(Command::name) - .filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase())) - .collect(Collectors.toList()); - - return Collections.emptyList(); - } + return Collections.emptyList(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/ClearCacheCommand.java b/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/ClearCacheCommand.java index 01ec9a5..7688d41 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/ClearCacheCommand.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/ClearCacheCommand.java @@ -17,57 +17,58 @@ package dev.brighten.antivpn.command.impl; import dev.brighten.antivpn.AntiVPN; -import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.CommandExecutor; - import java.util.Collections; import java.util.List; public class ClearCacheCommand extends Command { - @Override - public String permission() { - return "antivpn.command.clearcache"; - } + @Override + public String permission() { + return "antivpn.command.clearcache"; + } - @Override - public String name() { - return "clearcache"; - } + @Override + public String name() { + return "clearcache"; + } - @Override - public String[] aliases() { - return new String[] {"clear", "cc"}; - } + @Override + public String[] aliases() { + return new String[] {"clear", "cc"}; + } - @Override - public String description() { - return "Clear the API response cache if you're having problems."; - } + @Override + public String description() { + return "Clear the API response cache if you're having problems."; + } - @Override - public String usage() { - return ""; - } + @Override + public String usage() { + return ""; + } - @Override - public String parent() { - return "antivpn"; - } + @Override + public String parent() { + return "antivpn"; + } - @Override - public Command[] children() { - return new Command[0]; - } + @Override + public Command[] children() { + return new Command[0]; + } - @Override - public String execute(CommandExecutor executor, String[] args) { - AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> AntiVPN.getInstance().getDatabase().clearResponses()); - return "&aCleared all cached API response information!"; - } + @Override + public String execute(CommandExecutor executor, String[] args) { + AntiVPN.getInstance() + .getExecutor() + .getThreadExecutor() + .execute(() -> AntiVPN.getInstance().getDatabase().clearResponses()); + return "&aCleared all cached API response information!"; + } - @Override - public List tabComplete(CommandExecutor executor, String alias, String[] args) { - return Collections.emptyList(); - } + @Override + public List tabComplete(CommandExecutor executor, String alias, String[] args) { + return Collections.emptyList(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/LookupCommand.java b/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/LookupCommand.java index 4a123e2..fb0d1c3 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/LookupCommand.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/LookupCommand.java @@ -21,94 +21,97 @@ import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.utils.StringUtil; - import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; public class LookupCommand extends Command { - @Override - public String permission() { - return "antivpn.command.lookup"; + @Override + public String permission() { + return "antivpn.command.lookup"; + } + + @Override + public String name() { + return "lookup"; + } + + @Override + public String[] aliases() { + return new String[] {"check"}; + } + + @Override + public String description() { + return "Lookup a player's ip info"; + } + + @Override + public String usage() { + return ""; + } + + @Override + public String parent() { + return "antivpn"; + } + + @Override + public Command[] children() { + return new Command[0]; + } + + @Override + public String execute(CommandExecutor executor, String[] args) { + if (args.length == 0) { + return "&cPlease supply a player to check."; } - @Override - public String name() { - return "lookup"; + Optional player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(args[0]); + + if (player.isEmpty()) { + return String.format("&cNo player found with the name \"%s\"", args[0]); } - @Override - public String[] aliases() { - return new String[] {"check"}; - } + AntiVPN.getInstance() + .getExecutor() + .checkIp(player.get().getIp().getHostAddress()) + .thenAccept( + result -> { + if (!result.isSuccess()) { + executor.sendMessage( + "&cThere was an error trying to find the " + "information of this player."); + return; + } - @Override - public String description() { - return "Lookup a player's ip info"; - } + executor.sendMessage(StringUtil.line("&8")); + executor.sendMessage( + "&6&l" + player.get().getName() + "&7&l's Connection Information"); + executor.sendMessage(""); + executor.sendMessage( + "&e%s&8: &f%s", "Proxy", result.isProxy() ? "&a" + result.getMethod() : "&cNo"); + executor.sendMessage("&e%s&8: &f%s", "ISP", result.getIsp()); + executor.sendMessage("&e%s&8: &f%s", "Country", result.getCountryName()); + executor.sendMessage("&e%s&8: &f%s", "City", result.getCity()); + executor.sendMessage( + "&e%s&8: &f%s", + "Coordinates", result.getLatitude() + "&7/&f" + result.getLongitude()); + executor.sendMessage(StringUtil.line("&8")); + }); - @Override - public String usage() { - return ""; - } + return "&7Looking up the IP information for player " + player.get().getName() + "..."; + } - @Override - public String parent() { - return "antivpn"; - } + @Override + public List tabComplete(CommandExecutor executor, String alias, String[] args) { - @Override - public Command[] children() { - return new Command[0]; - } + if (args.length == 1) + return AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream() + .map(APIPlayer::getName) + .filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase())) + .collect(Collectors.toList()); - @Override - public String execute(CommandExecutor executor, String[] args) { - if(args.length == 0) { - return "&cPlease supply a player to check."; - } - - Optional player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(args[0]); - - if(player.isEmpty()) { - return String.format("&cNo player found with the name \"%s\"", args[0]); - } - - AntiVPN.getInstance().getExecutor() - .checkIp(player.get().getIp().getHostAddress()) - .thenAccept(result -> { - if(!result.isSuccess()) { - executor.sendMessage("&cThere was an error trying to find the " + - "information of this player."); - return; - } - - executor.sendMessage(StringUtil.line("&8")); - executor.sendMessage("&6&l" + player.get().getName() + "&7&l's Connection Information"); - executor.sendMessage(""); - executor.sendMessage("&e%s&8: &f%s", "Proxy", result.isProxy() - ? "&a" + result.getMethod() : "&cNo"); - executor.sendMessage("&e%s&8: &f%s", "ISP", result.getIsp()); - executor.sendMessage("&e%s&8: &f%s", "Country", result.getCountryName()); - executor.sendMessage("&e%s&8: &f%s", "City", result.getCity()); - executor.sendMessage("&e%s&8: &f%s", "Coordinates", result.getLatitude() - + "&7/&f" + result.getLongitude()); - executor.sendMessage(StringUtil.line("&8")); - }); - - - return "&7Looking up the IP information for player " + player.get().getName() + "..."; - } - - @Override - public List tabComplete(CommandExecutor executor, String alias, String[] args) { - - if(args.length == 1) return AntiVPN.getInstance().getPlayerExecutor().getOnlinePlayers().stream() - .map(APIPlayer::getName) - .filter(name -> name.toLowerCase().startsWith(args[0].toLowerCase())) - .collect(Collectors.toList()); - - return Collections.emptyList(); - } + return Collections.emptyList(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/PlanCommand.java b/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/PlanCommand.java index 0151b44..f5e1809 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/PlanCommand.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/PlanCommand.java @@ -17,101 +17,110 @@ package dev.brighten.antivpn.command.impl; import dev.brighten.antivpn.AntiVPN; -import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.utils.StringUtil; import dev.brighten.antivpn.utils.json.JSONException; import dev.brighten.antivpn.web.FunkemunkyAPI; import dev.brighten.antivpn.web.objects.QueryResponse; - import java.io.IOException; import java.util.Collections; import java.util.List; public class PlanCommand extends Command { - @Override - public String permission() { - return "antivpn.command.plan"; - } + @Override + public String permission() { + return "antivpn.command.plan"; + } - @Override - public String name() { - return "plan"; - } + @Override + public String name() { + return "plan"; + } - @Override - public String[] aliases() { - return new String[] {"queries", "query"}; - } + @Override + public String[] aliases() { + return new String[] {"queries", "query"}; + } - @Override - public String description() { - return "Info related to KauriVPN Plan"; - } + @Override + public String description() { + return "Info related to KauriVPN Plan"; + } - @Override - public String usage() { - return ""; - } + @Override + public String usage() { + return ""; + } - @Override - public String parent() { - return "antivpn"; - } + @Override + public String parent() { + return "antivpn"; + } - @Override - public Command[] children() { - return new Command[0]; - } + @Override + public Command[] children() { + return new Command[0]; + } - @Override - public String execute(CommandExecutor executor, String[] args) { - AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> { - QueryResponse result; - try { - if(AntiVPN.getInstance().getVpnConfig().getLicense().isEmpty()) { - result = FunkemunkyAPI.getQueryResponse(); + @Override + public String execute(CommandExecutor executor, String[] args) { + AntiVPN.getInstance() + .getExecutor() + .getThreadExecutor() + .execute( + () -> { + QueryResponse result; + try { + if (AntiVPN.getInstance().getVpnConfig().getLicense().isEmpty()) { + result = FunkemunkyAPI.getQueryResponse(); } else { - result = FunkemunkyAPI.getQueryResponse(AntiVPN.getInstance().getVpnConfig().getLicense()); + result = + FunkemunkyAPI.getQueryResponse( + AntiVPN.getInstance().getVpnConfig().getLicense()); - if(!result.isValidPlan()) { - executor.sendMessage("&cThe license &f%s &cis not a valid license, " + - "checking your Free plan information...", - AntiVPN.getInstance().getVpnConfig().getLicense()); + if (!result.isValidPlan()) { + executor.sendMessage( + "&cThe license &f%s &cis not a valid license, " + + "checking your Free plan information...", + AntiVPN.getInstance().getVpnConfig().getLicense()); - result = FunkemunkyAPI.getQueryResponse(); - } + result = FunkemunkyAPI.getQueryResponse(); + } } String plan = result.getPlanType(); - if(plan.equals("IP")) plan+= " (Free)"; + if (plan.equals("IP")) plan += " (Free)"; - String queryMax = result.getQueriesMax() == Long.MAX_VALUE - ? "Unlimited" : String.valueOf(result.getQueriesMax()); + String queryMax = + result.getQueriesMax() == Long.MAX_VALUE + ? "Unlimited" + : String.valueOf(result.getQueriesMax()); executor.sendMessage(StringUtil.line("&8")); executor.sendMessage("&6&lKauriVPN Plan Information"); executor.sendMessage(""); executor.sendMessage("&e%s&8: &f%s", "Plan", plan); - executor.sendMessage("&e%s&8: &f%s&7/&f%s", "Queries Used", - result.getQueries(), queryMax); + executor.sendMessage( + "&e%s&8: &f%s&7/&f%s", "Queries Used", result.getQueries(), queryMax); executor.sendMessage(StringUtil.line("&8")); - } catch(JSONException e) { + } catch (JSONException e) { AntiVPN.getInstance().getExecutor().logException(e); - executor.sendMessage("&cThere was a JSONException thrown while looking up your query " + - "information. Check console for more details."); - } catch (IOException e) { + executor.sendMessage( + "&cThere was a JSONException thrown while looking up your query " + + "information. Check console for more details."); + } catch (IOException e) { AntiVPN.getInstance().getExecutor().logException(e); - executor.sendMessage("&cThere was a IOException thrown while looking up your query " + - "information. Check console for more details."); - } - }); - return "&7Looking up your query information..."; - } + executor.sendMessage( + "&cThere was a IOException thrown while looking up your query " + + "information. Check console for more details."); + } + }); + return "&7Looking up your query information..."; + } - @Override - public List tabComplete(CommandExecutor executor, String alias, String[] args) { - return Collections.emptyList(); - } + @Override + public List tabComplete(CommandExecutor executor, String alias, String[] args) { + return Collections.emptyList(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/ReloadCommand.java b/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/ReloadCommand.java index 5e3b97b..917857a 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/ReloadCommand.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/command/impl/ReloadCommand.java @@ -19,63 +19,65 @@ package dev.brighten.antivpn.command.impl; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.CommandExecutor; - import java.util.Collections; import java.util.List; public class ReloadCommand extends Command { - @Override - public String permission() { - return "antivpn.command.reload"; - } + @Override + public String permission() { + return "antivpn.command.reload"; + } - @Override - public String name() { - return "reload"; - } + @Override + public String name() { + return "reload"; + } - @Override - public String[] aliases() { - return new String[0]; - } + @Override + public String[] aliases() { + return new String[0]; + } - @Override - public String description() { - return "Reload the plugin"; - } + @Override + public String description() { + return "Reload the plugin"; + } - @Override - public String usage() { - return ""; - } + @Override + public String usage() { + return ""; + } - @Override - public String parent() { - return "antivpn"; - } + @Override + public String parent() { + return "antivpn"; + } - @Override - public Command[] children() { - return new Command[0]; - } + @Override + public Command[] children() { + return new Command[0]; + } - @Override - public String execute(CommandExecutor executor, String[] args) { - // Loading changes from the config.yml - AntiVPN.getInstance().reloadConfig(); + @Override + public String execute(CommandExecutor executor, String[] args) { + // Loading changes from the config.yml + AntiVPN.getInstance().reloadConfig(); - // Updating the cache of these values in VPNConfig - AntiVPN.getInstance().getVpnConfig().update(); + // Updating the cache of these values in VPNConfig + AntiVPN.getInstance().getVpnConfig().update(); - AntiVPN.getInstance().getMessageHandler().reloadStrings(); + AntiVPN.getInstance().getMessageHandler().reloadStrings(); - AntiVPN.getInstance().reloadDatabase(); + AntiVPN.getInstance().reloadDatabase(); - return AntiVPN.getInstance().getMessageHandler().getString("command-reload-complete").getMessage(); - } + return AntiVPN.getInstance() + .getMessageHandler() + .getString("command-reload-complete") + .getMessage(); + } - @Override - public List tabComplete(CommandExecutor executor, String alias, String[] args) { - return Collections.emptyList(); - } + @Override + public List tabComplete(CommandExecutor executor, String alias, String[] args) { + return Collections.emptyList(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/DatabaseException.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/DatabaseException.java index 53c62ef..3401693 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/DatabaseException.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/DatabaseException.java @@ -17,7 +17,7 @@ package dev.brighten.antivpn.database; public class DatabaseException extends RuntimeException { - public DatabaseException(String message, Throwable e) { - super(message, e); - } + public DatabaseException(String message, Throwable e) { + super(message, e); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/VPNDatabase.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/VPNDatabase.java index 02b958e..6c49b78 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/VPNDatabase.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/VPNDatabase.java @@ -18,44 +18,43 @@ package dev.brighten.antivpn.database; import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.web.objects.VPNResponse; - import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.function.Consumer; public interface VPNDatabase { - Optional getStoredResponse(String ip); + Optional getStoredResponse(String ip); - void cacheResponse(VPNResponse toCache); + void cacheResponse(VPNResponse toCache); - void deleteResponse(String ip); + void deleteResponse(String ip); - boolean isWhitelisted(UUID uuid); + boolean isWhitelisted(UUID uuid); - boolean isWhitelisted(String cidr); + boolean isWhitelisted(String cidr); - boolean isWhitelisted(CIDRUtils cidr); + boolean isWhitelisted(CIDRUtils cidr); - void addWhitelist(UUID uuid); + void addWhitelist(UUID uuid); - void removeWhitelist(UUID uuid); + void removeWhitelist(UUID uuid); - void addWhitelist(CIDRUtils cidr); + void addWhitelist(CIDRUtils cidr); - void removeWhitelist(CIDRUtils cidr); + void removeWhitelist(CIDRUtils cidr); - List getAllWhitelisted(); + List getAllWhitelisted(); - List getAllWhitelistedIps(); + List getAllWhitelistedIps(); - void alertsState(UUID uuid, Consumer result); + void alertsState(UUID uuid, Consumer result); - void updateAlertsState(UUID uuid, boolean state); + void updateAlertsState(UUID uuid, boolean state); - void clearResponses(); + void clearResponses(); - void init(); + void init(); - void shutdown(); -} \ No newline at end of file + void shutdown(); +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java index 1fd5bce..fbe5de8 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/H2VPN.java @@ -24,8 +24,6 @@ import dev.brighten.antivpn.database.sql.utils.Query; import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.web.objects.VPNResponse; -import lombok.SneakyThrows; - import java.io.File; import java.math.BigInteger; import java.net.UnknownHostException; @@ -35,346 +33,421 @@ import java.sql.Timestamp; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import lombok.SneakyThrows; public class H2VPN implements VPNDatabase { - - public H2VPN() { - AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> { - if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; - - //Refreshing whitelisted players - AntiVPN.getInstance().getExecutor().getWhitelisted().clear(); - AntiVPN.getInstance().getExecutor().getWhitelisted() - .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted()); - - //Refreshing whitlisted IPs - AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear(); - AntiVPN.getInstance().getExecutor().getWhitelistedIps() - .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()); - }, 2, 30, TimeUnit.SECONDS); - } - - @Override - public Optional getStoredResponse(String ip) { - if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()|| MySQL.isClosed()) - return Optional.empty(); - - try(ExecutableStatement statement = Query.prepare("select * from `responses` where `ip` = ? limit 1").append(ip)) { - try(ResultSet rs = statement.executeQuery()) { - if (rs != null && rs.next()) { - return Optional.of(new VPNResponse(rs.getString("asn"), rs.getString("ip"), - rs.getString("countryName"), rs.getString("countryCode"), - rs.getString("city"), rs.getString("timeZone"), - rs.getString("method"), rs.getString("isp"), "N/A", - rs.getBoolean("proxy"), rs.getBoolean("cached"), true, - rs.getDouble("latitude"), rs.getDouble("longitude"), - rs.getTimestamp("inserted").getTime(), -1)); - } - } - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("There was a problem getting a response for " - + ip, e); - } catch (Exception e) { - throw new RuntimeException(e); - } - return Optional.empty(); - } - - /* - * Query. - * prepare("create table if not exists `responses` (`ip` varchar(45) not null, " - * + - * "`countryName` varchar(64), `countryCode` varchar(10), `city` varchar(64), `timeZone` varchar(64), " - * + - * "`method` varchar(32), `isp` varchar(32), `proxy` boolean, `cached` boolean " - * + "`latitude` double, `longitude` double)"); - */ - @Override - public void cacheResponse(VPNResponse toCache) { - if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) - return; - - try(var statement = Query.prepare("insert into `responses` (`ip`,`asn`,`countryName`,`countryCode`,`city`,`timeZone`," - + "`method`,`isp`,`proxy`,`cached`,`inserted`,`latitude`,`longitude`) values (?,?,?,?,?,?,?,?,?,?,?,?,?)") - .append(toCache.getIp()).append(toCache.getAsn()).append(toCache.getCountryName()) - .append(toCache.getCountryCode()).append(toCache.getCity()).append(toCache.getTimeZone()) - .append(toCache.getMethod()).append(toCache.getIsp()).append(toCache.isProxy()) - .append(toCache.isCached()).append(new Timestamp(System.currentTimeMillis())) - .append(toCache.getLatitude()).append(toCache.getLongitude())) { - statement.execute(); - } catch(SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not cache response for IP: " + toCache.getIp(), e); - } - } - - @Override - public void deleteResponse(String ip) { - if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) - return; - - try(var statement = Query.prepare("delete from `responses` where `ip` = ?").append(ip)) { - statement.execute(); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not delete response from IP: " + ip, e); - } - } - - @Override - public boolean isWhitelisted(UUID uuid) { - if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) - return false; - - try(var statement = Query.prepare("select uuid from `whitelisted` where `uuid` = ? limit 1") - .append(uuid.toString())) { - try(var set = statement.executeQuery()) { - return set != null && set.next() && set.getString("uuid") != null; - } - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not check whitelist for uuid '" + uuid + "' due to SQL error.", e); - return false; - } - } - - @SneakyThrows - @Override - public boolean isWhitelisted(String cidr) { - return isWhitelisted(new CIDRUtils(cidr)); - } - - @Override - public boolean isWhitelisted(CIDRUtils cidr) { - if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) - return false; - - BigInteger start = cidr.getStartIpInt(); - BigInteger end = cidr.getEndIpInt(); - - try(var statement = Query.prepare("SELECT * FROM `whitelisted-ranges` WHERE ip_start <= ? AND ip_end >= ?") - .append(start).append(end)) { - - try(var result = statement.executeQuery()) { - return result.next(); - } - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not check whitelist for cidr '" + cidr + "' due to SQL error.", e); - } - return false; - } - - @Override - public void addWhitelist(UUID uuid) { - if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) - return; - - try(var statement = Query.prepare("insert into `whitelisted` (`uuid`) values (?)").append(uuid.toString())) { - statement.execute(); - AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not add uuid '" + uuid + "' to whitelist due to SQL error.", e); - } - } - - @Override - public void removeWhitelist(UUID uuid) { - if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) - return; - try(var statement = Query.prepare("delete from `whitelisted` where `uuid` = ?").append(uuid.toString())) { - statement.execute(); - AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not remove uuid '" + uuid + "' from whitelist due to SQL error.", e); - } - } - - @Override - public void addWhitelist(CIDRUtils cidr) { - if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) - return; - - try(var statement = Query.prepare("insert into `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) values (?, ?, ?)") - .append(cidr.getCidr()).append(cidr.getStartIpInt()).append(cidr.getEndIpInt())) { - statement.execute(); - - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not add cidr '" + cidr + "' to whitelist due to SQL error.", e); - } - } - - @Override - public void removeWhitelist(CIDRUtils cidr) { - if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) - return; - - try(var statement = Query.prepare("delete from `whitelisted-ranges` where `cidr_string` = ?").append(cidr.getCidr())) { - statement.execute(); - - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not remove cidr '" + cidr + "' from whitelist due to SQL error.", e); - } - } - - @Override - public List getAllWhitelisted() { - List uuids = new ArrayList<>(); - - if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) - return uuids; - - try(var statement = Query.prepare("select uuid from `whitelisted`")) { - statement.execute(set -> uuids.add(UUID.fromString(set.getString("uuid")))); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not get all whitelisted players due to SQL error.", e); - } - - return uuids; - } - - @Override - public List getAllWhitelistedIps() { - List ips = new ArrayList<>(); - - if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) - return ips; - try(var statement = Query.prepare("select `cidr_string`, `ip_start`, `ip_end` from `whitelisted-ranges`")) { - statement.execute(set -> { - try { - String cidrString = set.getString("cidr_string"); - - ips.add(new CIDRUtils(cidrString)); - - } catch (UnknownHostException e) { - AntiVPN.getInstance().getExecutor() - .logException("Could not format ip " - + set.getString("cidr_string") + " into a CIDR!", e); - } - }); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not get all whitelisted ips due to SQL error.", e); - } - - return ips; - } - - @Override - public void alertsState(UUID uuid, Consumer result) { - if(MySQL.isClosed()) return; - AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> { - - try(var statement = Query.prepare("select * from `alerts` where `uuid` = ? limit 1") - .append(uuid.toString())) { - try(var set = statement.executeQuery()) { - result.accept(set != null && set.next() && set.getString("uuid") != null); - } - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("There was a problem getting alerts state for " + uuid, e); - result.accept(false); - } - }); - } - - @Override - public void updateAlertsState(UUID uuid, boolean enabled) { - if(MySQL.isClosed()) return; - - if(enabled) { - //We want to make sure there isn't already a uuid inserted to prevent double insertions - alertsState(uuid, alreadyEnabled -> { //No need to make another thread execute, already async - if(!alreadyEnabled) { - try(var statement = Query.prepare("insert into `alerts` (`uuid`) values (?)") - .append(uuid.toString())) { - statement.execute(); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor() - .logException("There was a problem updating alerts state for " + uuid, e); - } - } //No need to insert again of already enabled - }); - //Removing any uuid from the alerts table will disable alerts globally. - } else { - try(var statement = Query.prepare("delete from `alerts` where `uuid` = ?").append(uuid.toString())) { - statement.execute(); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("There was a problem updating alerts state for " - + uuid, e); - } - } - } - - @Override - public void clearResponses() { - if(MySQL.isClosed()) return; - - try(var statement = Query.prepare("delete from `responses`")) { - statement.execute(); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("There was a problem clearing responses.", e); - } - } - - @Override - public void init() { - if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) - return; - AntiVPN.getInstance().getExecutor().log("Initializing H2..."); - MySQL.initH2(); - try { - for (Version version : Version.h2Versions) { - if(version.needsUpdate(this)) { - version.update(this); - } - } - } catch (Exception e) { - throw new RuntimeException("Could not complete version setup due to SQL error", e); - } - - AntiVPN.getInstance().getExecutor().log("Creating tables..."); - - //Running check for old table types to update - } - - @Override - public void shutdown() { - if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) - return; - - MySQL.shutdown(); - } - - public void backupDatabase() { - File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases"); - - if(!dataFolder.exists() || MySQL.isClosed()) { - return; - } - - try { - var connection = Query.getConn(); - if (connection == null || connection.getMetaData() == null - || !connection.getMetaData().getDatabaseProductName().equalsIgnoreCase("H2")) { + public H2VPN() { + AntiVPN.getInstance() + .getExecutor() + .getThreadExecutor() + .scheduleAtFixedRate( + () -> { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; - } - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not verify database type before H2 backup.", e); - return; - } - File backupDir = new File(dataFolder, "backups"); - if (!backupDir.exists() && !backupDir.mkdirs()) { - AntiVPN.getInstance().getExecutor().log("Could not create backup directory"); - return; - } + // Refreshing whitelisted players + AntiVPN.getInstance().getExecutor().getWhitelisted().clear(); + AntiVPN.getInstance() + .getExecutor() + .getWhitelisted() + .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted()); - File backupFile = new File(backupDir, "database.h2_backup_" + System.currentTimeMillis() + ".zip"); - String backupPath = backupFile.getAbsolutePath() - .replace("\\", "/") - .replace("'", "''"); + // Refreshing whitlisted IPs + AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear(); + AntiVPN.getInstance() + .getExecutor() + .getWhitelistedIps() + .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()); + }, + 2, + 30, + TimeUnit.SECONDS); + } - try (var statement = Query.prepare("BACKUP TO '" + backupPath + "'")) { - statement.execute(); - AntiVPN.getInstance().getExecutor().log("Created H2 backup at " + backupFile.getName()); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not create H2 backup before migration.", e); + @Override + public Optional getStoredResponse(String ip) { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) + return Optional.empty(); + + try (ExecutableStatement statement = + Query.prepare("select * from `responses` where `ip` = ? limit 1").append(ip)) { + try (ResultSet rs = statement.executeQuery()) { + if (rs != null && rs.next()) { + return Optional.of( + new VPNResponse( + rs.getString("asn"), + rs.getString("ip"), + rs.getString("countryName"), + rs.getString("countryCode"), + rs.getString("city"), + rs.getString("timeZone"), + rs.getString("method"), + rs.getString("isp"), + "N/A", + rs.getBoolean("proxy"), + rs.getBoolean("cached"), + true, + rs.getDouble("latitude"), + rs.getDouble("longitude"), + rs.getTimestamp("inserted").getTime(), + -1)); } + } + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("There was a problem getting a response for " + ip, e); + } catch (Exception e) { + throw new RuntimeException(e); } + return Optional.empty(); + } + + /* + * Query. + * prepare("create table if not exists `responses` (`ip` varchar(45) not null, " + * + + * "`countryName` varchar(64), `countryCode` varchar(10), `city` varchar(64), `timeZone` varchar(64), " + * + + * "`method` varchar(32), `isp` varchar(32), `proxy` boolean, `cached` boolean " + * + "`latitude` double, `longitude` double)"); + */ + @Override + public void cacheResponse(VPNResponse toCache) { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; + + try (var statement = + Query.prepare( + "insert into `responses` (`ip`,`asn`,`countryName`,`countryCode`,`city`,`timeZone`," + + "`method`,`isp`,`proxy`,`cached`,`inserted`,`latitude`,`longitude`) values (?,?,?,?,?,?,?,?,?,?,?,?,?)") + .append(toCache.getIp()) + .append(toCache.getAsn()) + .append(toCache.getCountryName()) + .append(toCache.getCountryCode()) + .append(toCache.getCity()) + .append(toCache.getTimeZone()) + .append(toCache.getMethod()) + .append(toCache.getIsp()) + .append(toCache.isProxy()) + .append(toCache.isCached()) + .append(new Timestamp(System.currentTimeMillis())) + .append(toCache.getLatitude()) + .append(toCache.getLongitude())) { + statement.execute(); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not cache response for IP: " + toCache.getIp(), e); + } + } + + @Override + public void deleteResponse(String ip) { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; + + try (var statement = Query.prepare("delete from `responses` where `ip` = ?").append(ip)) { + statement.execute(); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not delete response from IP: " + ip, e); + } + } + + @Override + public boolean isWhitelisted(UUID uuid) { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return false; + + try (var statement = + Query.prepare("select uuid from `whitelisted` where `uuid` = ? limit 1") + .append(uuid.toString())) { + try (var set = statement.executeQuery()) { + return set != null && set.next() && set.getString("uuid") != null; + } + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not check whitelist for uuid '" + uuid + "' due to SQL error.", e); + return false; + } + } + + @SneakyThrows + @Override + public boolean isWhitelisted(String cidr) { + return isWhitelisted(new CIDRUtils(cidr)); + } + + @Override + public boolean isWhitelisted(CIDRUtils cidr) { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return false; + + BigInteger start = cidr.getStartIpInt(); + BigInteger end = cidr.getEndIpInt(); + + try (var statement = + Query.prepare("SELECT * FROM `whitelisted-ranges` WHERE ip_start <= ? AND ip_end >= ?") + .append(start) + .append(end)) { + + try (var result = statement.executeQuery()) { + return result.next(); + } + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not check whitelist for cidr '" + cidr + "' due to SQL error.", e); + } + return false; + } + + @Override + public void addWhitelist(UUID uuid) { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; + + try (var statement = + Query.prepare("insert into `whitelisted` (`uuid`) values (?)").append(uuid.toString())) { + statement.execute(); + AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not add uuid '" + uuid + "' to whitelist due to SQL error.", e); + } + } + + @Override + public void removeWhitelist(UUID uuid) { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; + try (var statement = + Query.prepare("delete from `whitelisted` where `uuid` = ?").append(uuid.toString())) { + statement.execute(); + AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not remove uuid '" + uuid + "' from whitelist due to SQL error.", e); + } + } + + @Override + public void addWhitelist(CIDRUtils cidr) { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; + + try (var statement = + Query.prepare( + "insert into `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) values (?, ?, ?)") + .append(cidr.getCidr()) + .append(cidr.getStartIpInt()) + .append(cidr.getEndIpInt())) { + statement.execute(); + + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not add cidr '" + cidr + "' to whitelist due to SQL error.", e); + } + } + + @Override + public void removeWhitelist(CIDRUtils cidr) { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; + + try (var statement = + Query.prepare("delete from `whitelisted-ranges` where `cidr_string` = ?") + .append(cidr.getCidr())) { + statement.execute(); + + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not remove cidr '" + cidr + "' from whitelist due to SQL error.", e); + } + } + + @Override + public List getAllWhitelisted() { + List uuids = new ArrayList<>(); + + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return uuids; + + try (var statement = Query.prepare("select uuid from `whitelisted`")) { + statement.execute(set -> uuids.add(UUID.fromString(set.getString("uuid")))); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not get all whitelisted players due to SQL error.", e); + } + + return uuids; + } + + @Override + public List getAllWhitelistedIps() { + List ips = new ArrayList<>(); + + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return ips; + try (var statement = + Query.prepare("select `cidr_string`, `ip_start`, `ip_end` from `whitelisted-ranges`")) { + statement.execute( + set -> { + try { + String cidrString = set.getString("cidr_string"); + + ips.add(new CIDRUtils(cidrString)); + + } catch (UnknownHostException e) { + AntiVPN.getInstance() + .getExecutor() + .logException( + "Could not format ip " + set.getString("cidr_string") + " into a CIDR!", e); + } + }); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not get all whitelisted ips due to SQL error.", e); + } + + return ips; + } + + @Override + public void alertsState(UUID uuid, Consumer result) { + if (MySQL.isClosed()) return; + AntiVPN.getInstance() + .getExecutor() + .getThreadExecutor() + .execute( + () -> { + try (var statement = + Query.prepare("select * from `alerts` where `uuid` = ? limit 1") + .append(uuid.toString())) { + try (var set = statement.executeQuery()) { + result.accept(set != null && set.next() && set.getString("uuid") != null); + } + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("There was a problem getting alerts state for " + uuid, e); + result.accept(false); + } + }); + } + + @Override + public void updateAlertsState(UUID uuid, boolean enabled) { + if (MySQL.isClosed()) return; + + if (enabled) { + // We want to make sure there isn't already a uuid inserted to prevent double insertions + alertsState( + uuid, + alreadyEnabled -> { // No need to make another thread execute, already async + if (!alreadyEnabled) { + try (var statement = + Query.prepare("insert into `alerts` (`uuid`) values (?)") + .append(uuid.toString())) { + statement.execute(); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("There was a problem updating alerts state for " + uuid, e); + } + } // No need to insert again of already enabled + }); + // Removing any uuid from the alerts table will disable alerts globally. + } else { + try (var statement = + Query.prepare("delete from `alerts` where `uuid` = ?").append(uuid.toString())) { + statement.execute(); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("There was a problem updating alerts state for " + uuid, e); + } + } + } + + @Override + public void clearResponses() { + if (MySQL.isClosed()) return; + + try (var statement = Query.prepare("delete from `responses`")) { + statement.execute(); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("There was a problem clearing responses.", e); + } + } + + @Override + public void init() { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return; + AntiVPN.getInstance().getExecutor().log("Initializing H2..."); + MySQL.initH2(); + try { + for (Version version : Version.h2Versions) { + if (version.needsUpdate(this)) { + version.update(this); + } + } + } catch (Exception e) { + throw new RuntimeException("Could not complete version setup due to SQL error", e); + } + + AntiVPN.getInstance().getExecutor().log("Creating tables..."); + + // Running check for old table types to update + } + + @Override + public void shutdown() { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return; + + MySQL.shutdown(); + } + + public void backupDatabase() { + File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases"); + + if (!dataFolder.exists() || MySQL.isClosed()) { + return; + } + + try { + var connection = Query.getConn(); + if (connection == null + || connection.getMetaData() == null + || !connection.getMetaData().getDatabaseProductName().equalsIgnoreCase("H2")) { + return; + } + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not verify database type before H2 backup.", e); + return; + } + + File backupDir = new File(dataFolder, "backups"); + if (!backupDir.exists() && !backupDir.mkdirs()) { + AntiVPN.getInstance().getExecutor().log("Could not create backup directory"); + return; + } + + File backupFile = + new File(backupDir, "database.h2_backup_" + System.currentTimeMillis() + ".zip"); + String backupPath = backupFile.getAbsolutePath().replace("\\", "/").replace("'", "''"); + + try (var statement = Query.prepare("BACKUP TO '" + backupPath + "'")) { + statement.execute(); + AntiVPN.getInstance().getExecutor().log("Created H2 backup at " + backupFile.getName()); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not create H2 backup before migration.", e); + } + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/First.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/First.java index 7b78bbb..7b810e1 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/First.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/First.java @@ -23,7 +23,6 @@ import dev.brighten.antivpn.database.sql.utils.ExecutableStatement; import dev.brighten.antivpn.database.sql.utils.Query; import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.utils.MiscUtils; - import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; @@ -32,99 +31,104 @@ import java.util.List; public class First implements Version { - private final List toClose = new ArrayList<>(); - @Override - public void update(VPNDatabase database) throws DatabaseException { - try { - closeOnEnd(Query.prepare("create table if not exists `whitelisted` (`uuid` varchar(36) not null)")) - .execute(); - closeOnEnd(Query.prepare("create table if not exists `whitelisted-ips` (`ip` varchar(45) not null)")) - .execute(); - closeOnEnd(Query - .prepare("create table if not exists `responses` (`ip` varchar(45) not null, `asn` varchar(12)," - + "`countryName` text, `countryCode` varchar(10), `city` text, `timeZone` varchar(64), " - + "`method` varchar(32), `isp` text, `proxy` boolean, `cached` boolean, `inserted` timestamp," - + "`latitude` double, `longitude` double)")).execute(); - closeOnEnd(Query.prepare("create table if not exists `alerts` (`uuid` varchar(36) not null)")) - .execute(); - closeOnEnd(Query.prepare("create table if not exists `database_version` (`version` int)")).execute(); - closeOnEnd(Query.prepare("insert into `database_version` (`version`) values (?)") - .append(versionNumber())).execute(); + private final List toClose = new ArrayList<>(); - AntiVPN.getInstance().getExecutor().log("Creating indexes..."); - createIndexIfAbsent("whitelisted", "whitelisted_uuid_1", "`uuid`"); - createIndexIfAbsent("responses", "responses_ip_1", "`ip`"); - createIndexIfAbsent("responses", "responses_proxy_1", "`proxy`"); - createIndexIfAbsent("responses", "responses_inserted_1", "`inserted`"); - createIndexIfAbsent("whitelisted-ips", "whitelisted_ips_ip_1", "`ip`"); - } catch (SQLException e) { - throw new DatabaseException("Failed to update database", e); - } finally { - MiscUtils.close(toClose.toArray(AutoCloseable[]::new)); - toClose.clear(); + @Override + public void update(VPNDatabase database) throws DatabaseException { + try { + closeOnEnd( + Query.prepare( + "create table if not exists `whitelisted` (`uuid` varchar(36) not null)")) + .execute(); + closeOnEnd( + Query.prepare( + "create table if not exists `whitelisted-ips` (`ip` varchar(45) not null)")) + .execute(); + closeOnEnd( + Query.prepare( + "create table if not exists `responses` (`ip` varchar(45) not null, `asn` varchar(12)," + + "`countryName` text, `countryCode` varchar(10), `city` text, `timeZone` varchar(64), " + + "`method` varchar(32), `isp` text, `proxy` boolean, `cached` boolean, `inserted` timestamp," + + "`latitude` double, `longitude` double)")) + .execute(); + closeOnEnd(Query.prepare("create table if not exists `alerts` (`uuid` varchar(36) not null)")) + .execute(); + closeOnEnd(Query.prepare("create table if not exists `database_version` (`version` int)")) + .execute(); + closeOnEnd( + Query.prepare("insert into `database_version` (`version`) values (?)") + .append(versionNumber())) + .execute(); + + AntiVPN.getInstance().getExecutor().log("Creating indexes..."); + createIndexIfAbsent("whitelisted", "whitelisted_uuid_1", "`uuid`"); + createIndexIfAbsent("responses", "responses_ip_1", "`ip`"); + createIndexIfAbsent("responses", "responses_proxy_1", "`proxy`"); + createIndexIfAbsent("responses", "responses_inserted_1", "`inserted`"); + createIndexIfAbsent("whitelisted-ips", "whitelisted_ips_ip_1", "`ip`"); + } catch (SQLException e) { + throw new DatabaseException("Failed to update database", e); + } finally { + MiscUtils.close(toClose.toArray(AutoCloseable[]::new)); + toClose.clear(); + } + } + + private ExecutableStatement closeOnEnd(ExecutableStatement statement) { + toClose.add(statement); + return statement; + } + + protected void createIndexIfAbsent(String tableName, String indexName, String columnList) + throws SQLException { + if (hasIndex(tableName, indexName)) { + return; + } + + closeOnEnd( + Query.prepare( + String.format("create index `%s` on `%s` (%s)", indexName, tableName, columnList))) + .execute(); + } + + protected void dropIndexIfPresent(String tableName, String indexName) throws SQLException { + if (!hasIndex(tableName, indexName)) { + return; + } + + closeOnEnd(Query.prepare(String.format("drop index `%s` on `%s`", indexName, tableName))) + .execute(); + } + + protected boolean hasIndex(String tableName, String indexName) throws SQLException { + DatabaseMetaData metaData = Query.getConn().getMetaData(); + + try (ResultSet indexes = metaData.getIndexInfo(null, null, tableName, false, false)) { + while (indexes.next()) { + String existingIndexName = indexes.getString("INDEX_NAME"); + + if (existingIndexName != null && existingIndexName.equalsIgnoreCase(indexName)) { + return true; } + } } - private ExecutableStatement closeOnEnd(ExecutableStatement statement) { - toClose.add(statement); - return statement; - } - - protected void createIndexIfAbsent(String tableName, String indexName, String columnList) throws SQLException { - if (hasIndex(tableName, indexName)) { - return; - } - - closeOnEnd(Query.prepare(String.format( - "create index `%s` on `%s` (%s)", - indexName, - tableName, - columnList - ))).execute(); - } - - protected void dropIndexIfPresent(String tableName, String indexName) throws SQLException { - if (!hasIndex(tableName, indexName)) { - return; - } - - closeOnEnd(Query.prepare(String.format( - "drop index `%s` on `%s`", - indexName, - tableName - ))).execute(); - } - - protected boolean hasIndex(String tableName, String indexName) throws SQLException { - DatabaseMetaData metaData = Query.getConn().getMetaData(); - - try (ResultSet indexes = metaData.getIndexInfo(null, null, tableName, false, false)) { - while (indexes.next()) { - String existingIndexName = indexes.getString("INDEX_NAME"); - - if (existingIndexName != null && existingIndexName.equalsIgnoreCase(indexName)) { - return true; - } - } - } - - return false; - } - - @Override - public int versionNumber() { - return 0; - } - - @Override - public boolean needsUpdate(VPNDatabase database) { - try(var statement = Query.prepare("select * from `database_version` where version = 0")) { - try(ResultSet set = statement.executeQuery()) { - return !set.next(); - } - } catch (SQLException e) { - return true; - } - + return false; + } + + @Override + public int versionNumber() { + return 0; + } + + @Override + public boolean needsUpdate(VPNDatabase database) { + try (var statement = Query.prepare("select * from `database_version` where version = 0")) { + try (ResultSet set = statement.executeQuery()) { + return !set.next(); + } + } catch (SQLException e) { + return true; } + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Second.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Second.java index fd81080..158c812 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Second.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Second.java @@ -26,133 +26,148 @@ import dev.brighten.antivpn.database.sql.utils.Query; import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.utils.MiscUtils; - import java.net.UnknownHostException; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class Second extends First implements Version { - private final List toClose = new ArrayList<>(); + private final List toClose = new ArrayList<>(); - @Override - public void update(VPNDatabase database) throws DatabaseException { - if(database instanceof H2VPN h2VPN && !(database instanceof MySqlVPN)) { - h2VPN.backupDatabase(); + @Override + public void update(VPNDatabase database) throws DatabaseException { + if (database instanceof H2VPN h2VPN && !(database instanceof MySqlVPN)) { + h2VPN.backupDatabase(); + } + List whitelistedIps = new ArrayList<>(); + + try (var statement = Query.prepare("SELECT * FROM `whitelisted-ips`")) { + try (var set = statement.executeQuery()) { + while (set.next()) { + whitelistedIps.add(set.getString("ip")); } - List whitelistedIps = new ArrayList<>(); - - try (var statement = Query.prepare("SELECT * FROM `whitelisted-ips`")) { - try(var set = statement.executeQuery()) { - while (set.next()) { - whitelistedIps.add(set.getString("ip")); - } - } - } catch (SQLException e) { - throw new DatabaseException("Could not get whitelisted ips from database!", e); - } - - try { - closeOnEnd(Query.prepare("CREATE TABLE IF NOT EXISTS `whitelisted-ranges` " + - "(id INT AUTO_INCREMENT PRIMARY KEY, " + - "cidr_string VARCHAR(45), " + - "ip_start BIGINT NOT NULL, " + - "ip_end BIGINT NOT NULL)")) - .execute(); - createIndexIfAbsent("whitelisted-ranges", "idx_ip_range", "ip_start, ip_end"); - - var cidrs = whitelistedIps.stream().map(ip -> { - try { - return new CIDRUtils(ip + "/32"); - } catch (UnknownHostException e) { - throw new RuntimeException("Could not format ip " + ip + " into a CIDR!", e); - } - }).toList(); - var insertStatement = Query.prepare("INSERT INTO `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) VALUES (?, ?, ?)"); - for (CIDRUtils cidr : cidrs) { - insertStatement = insertStatement - .append(cidr.toString()) - .append(cidr.getStartIpInt()) - .append(cidr.getEndIpInt()) - .addBatch(); - } - - int[] updateCounts = insertStatement.executeBatch(); - - for (int updateCount : updateCounts) { - if(updateCount == 0) { - throw new RuntimeException("Could not insert a CIDR from previous whitelisted lists, attempted to restore previous database!"); - } - } - - dropIndexIfPresent("whitelisted-ips", "ip_1"); - dropIndexIfPresent("whitelisted-ips", "whitelisted_ips_ip_1"); - closeOnEnd(Query.prepare("DROP TABLE `whitelisted-ips`")).execute(); - closeOnEnd(Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)").append(versionNumber())).execute(); - } catch (Throwable e) { - AntiVPN.getInstance().getExecutor().log("Failed to update database to version 1: " + e.getMessage()); - try { - rollback(whitelistedIps); - } catch (SQLException ex) { - throw new DatabaseException("Failed to rollback database!", e); - } - throw new DatabaseException("Failed to update to version one, rolling back database!", e); - } finally { - MiscUtils.close(toClose.toArray(AutoCloseable[]::new)); - toClose.clear(); - } - + } + } catch (SQLException e) { + throw new DatabaseException("Could not get whitelisted ips from database!", e); } - private ExecutableStatement closeOnEnd(ExecutableStatement statement) { - toClose.add(statement); - return statement; + try { + closeOnEnd( + Query.prepare( + "CREATE TABLE IF NOT EXISTS `whitelisted-ranges` " + + "(id INT AUTO_INCREMENT PRIMARY KEY, " + + "cidr_string VARCHAR(45), " + + "ip_start BIGINT NOT NULL, " + + "ip_end BIGINT NOT NULL)")) + .execute(); + createIndexIfAbsent("whitelisted-ranges", "idx_ip_range", "ip_start, ip_end"); + + var cidrs = + whitelistedIps.stream() + .map( + ip -> { + try { + return new CIDRUtils(ip + "/32"); + } catch (UnknownHostException e) { + throw new RuntimeException("Could not format ip " + ip + " into a CIDR!", e); + } + }) + .toList(); + var insertStatement = + Query.prepare( + "INSERT INTO `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) VALUES (?, ?, ?)"); + for (CIDRUtils cidr : cidrs) { + insertStatement = + insertStatement + .append(cidr.toString()) + .append(cidr.getStartIpInt()) + .append(cidr.getEndIpInt()) + .addBatch(); + } + + int[] updateCounts = insertStatement.executeBatch(); + + for (int updateCount : updateCounts) { + if (updateCount == 0) { + throw new RuntimeException( + "Could not insert a CIDR from previous whitelisted lists, attempted to restore previous database!"); + } + } + + dropIndexIfPresent("whitelisted-ips", "ip_1"); + dropIndexIfPresent("whitelisted-ips", "whitelisted_ips_ip_1"); + closeOnEnd(Query.prepare("DROP TABLE `whitelisted-ips`")).execute(); + closeOnEnd( + Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)") + .append(versionNumber())) + .execute(); + } catch (Throwable e) { + AntiVPN.getInstance() + .getExecutor() + .log("Failed to update database to version 1: " + e.getMessage()); + try { + rollback(whitelistedIps); + } catch (SQLException ex) { + throw new DatabaseException("Failed to rollback database!", e); + } + throw new DatabaseException("Failed to update to version one, rolling back database!", e); + } finally { + MiscUtils.close(toClose.toArray(AutoCloseable[]::new)); + toClose.clear(); + } + } + + private ExecutableStatement closeOnEnd(ExecutableStatement statement) { + toClose.add(statement); + return statement; + } + + private void rollback(List ipAddresses) throws SQLException { + AntiVPN.getInstance().getExecutor().log("Rolling back to version 0..."); + dropIndexIfPresent("whitelisted-ranges", "idx_ip_range"); + try (var statement = Query.prepare("DROP TABLE `whitelisted-ranges`")) { + statement.execute(); } - private void rollback(List ipAddresses) throws SQLException { - AntiVPN.getInstance().getExecutor().log("Rolling back to version 0..."); - dropIndexIfPresent("whitelisted-ranges", "idx_ip_range"); - try(var statement = Query.prepare("DROP TABLE `whitelisted-ranges`")) { - statement.execute(); - } - - try(var statement = Query.prepare("DELETE FROM `database_version` WHERE version = ?").append(versionNumber())) { - statement.execute(); - } - - try(var statement = Query.prepare("CREATE TABLE IF NOT EXISTS `whitelisted-ips` (`ip` VARCHAR(45) NOT NULL)")) { - statement.execute(); - } - - createIndexIfAbsent("whitelisted-ips", "whitelisted_ips_ip_1", "`ip`"); - - try(var statement = Query.prepare("DELETE FROM `whitelisted-ips`")) { - statement.execute(); - } - - try(var statement = Query.prepare("INSERT INTO `whitelisted-ips` (`ip`) VALUES (?)")) { - for (String ip : ipAddresses) { - statement.append(ip); - statement.addBatch(); - } - - statement.executeBatch(); - } + try (var statement = + Query.prepare("DELETE FROM `database_version` WHERE version = ?").append(versionNumber())) { + statement.execute(); } - @Override - public int versionNumber() { - return 1; + try (var statement = + Query.prepare("CREATE TABLE IF NOT EXISTS `whitelisted-ips` (`ip` VARCHAR(45) NOT NULL)")) { + statement.execute(); } - @Override - public boolean needsUpdate(VPNDatabase database) { - try (var statement = Query.prepare("select * from `database_version` where version = 1")) { - try(var set = statement.executeQuery()) { - return !set.next(); - } - } catch (SQLException e) { - return true; - } + createIndexIfAbsent("whitelisted-ips", "whitelisted_ips_ip_1", "`ip`"); + + try (var statement = Query.prepare("DELETE FROM `whitelisted-ips`")) { + statement.execute(); } + + try (var statement = Query.prepare("INSERT INTO `whitelisted-ips` (`ip`) VALUES (?)")) { + for (String ip : ipAddresses) { + statement.append(ip); + statement.addBatch(); + } + + statement.executeBatch(); + } + } + + @Override + public int versionNumber() { + return 1; + } + + @Override + public boolean needsUpdate(VPNDatabase database) { + try (var statement = Query.prepare("select * from `database_version` where version = 1")) { + try (var set = statement.executeQuery()) { + return !set.next(); + } + } catch (SQLException e) { + return true; + } + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Third.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Third.java index 4d19479..627a8ed 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Third.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/local/version/Third.java @@ -23,7 +23,6 @@ import dev.brighten.antivpn.database.sql.utils.Query; import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.utils.MiscUtils; - import java.math.BigInteger; import java.net.UnknownHostException; import java.sql.SQLException; @@ -32,84 +31,132 @@ import java.util.List; import java.util.logging.Level; public class Third implements Version { - @Override - public void update(VPNDatabase database) throws DatabaseException { - List ipRanges = new ArrayList<>(); - List rangesToInsert = new ArrayList<>(); - List rangesToRemove = new ArrayList<>(); - try (var preparedQuery = Query.prepare("select ip_start, ip_end from `whitelisted-ranges`")) { - preparedQuery.execute(set -> { - BigInteger start = set.getBigDecimal("ip_start").toBigInteger(); - BigInteger end = set.getBigDecimal("ip_end").toBigInteger(); + @Override + public void update(VPNDatabase database) throws DatabaseException { + List ipRanges = new ArrayList<>(); + List rangesToInsert = new ArrayList<>(); + List rangesToRemove = new ArrayList<>(); + try (var preparedQuery = Query.prepare("select ip_start, ip_end from `whitelisted-ranges`")) { + preparedQuery.execute( + set -> { + BigInteger start = set.getBigDecimal("ip_start").toBigInteger(); + BigInteger end = set.getBigDecimal("ip_end").toBigInteger(); - try { - var range = MiscUtils.rangeToCidrs(start, end); + try { + var range = MiscUtils.rangeToCidrs(start, end); - if(range.size() > 1) { - rangesToRemove.add(new BigInteger[]{start, end}); - rangesToInsert.addAll(range); - AntiVPN.getInstance().getExecutor().log(Level.WARNING, "Found multiple CIDR ranges for whitelist range for %s, %s!", start, end); - } else ipRanges.addAll(range); - } catch (UnknownHostException e) { - AntiVPN.getInstance().getExecutor().logException( - String.format("Could not convert ip range to CIDR! %s, %s", start, end), e); - } - }); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not get all whitelisted ranges due to SQL error.", e); - } - - AntiVPN.getInstance().getExecutor().log("Inserting %s new ranges into database...", rangesToInsert.size()); - - for (CIDRUtils cidr : rangesToInsert) { - try(var statement = Query.prepare("insert into `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) values (?, ?, ?)") - .append(cidr.getCidr()).append(cidr.getStartIpInt()).append(cidr.getEndIpInt())) { - statement.execute(); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not add cidr '" + cidr + "' to whitelist due to SQL error.", e); + if (range.size() > 1) { + rangesToRemove.add(new BigInteger[] {start, end}); + rangesToInsert.addAll(range); + AntiVPN.getInstance() + .getExecutor() + .log( + Level.WARNING, + "Found multiple CIDR ranges for whitelist range for %s, %s!", + start, + end); + } else ipRanges.addAll(range); + } catch (UnknownHostException e) { + AntiVPN.getInstance() + .getExecutor() + .logException( + String.format("Could not convert ip range to CIDR! %s, %s", start, end), e); } - } - - AntiVPN.getInstance().getExecutor().log("Removing %s old ranges from database...", rangesToRemove.size()); - - for (BigInteger[] range : rangesToRemove) { - try(var statement = Query.prepare("delete from `whitelisted-ranges` where `ip_start` = ? and `ip_end` = ?")) { - statement.append(range[0]).append(range[1]).execute(); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not remove cidr range '" + range[0] + ", " + range[1] + "' from whitelist due to SQL error.", e); - } - } - - AntiVPN.getInstance().getExecutor().log("Updating %s ranges to proper CIDR notation with the database", ipRanges.size()); - - for (CIDRUtils cidr : ipRanges) { - try(var statement = Query.prepare("update `whitelisted-ranges` set `cidr_string` = ? where `ip_start` = ? and `ip_end` = ?")) { - statement.append(cidr.getCidr()).append(cidr.getStartIpInt()).append(cidr.getEndIpInt()).execute(); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not update cidr '" + cidr + "' to proper CIDR notation in whitelist due to SQL error.", e); - } - } - - try (var preparedStatement = Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)").append(versionNumber())) { - preparedStatement.execute(); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException("Could not update database version to 2 due to SQL error.", e); - } + }); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not get all whitelisted ranges due to SQL error.", e); } - @Override - public int versionNumber() { - return 2; + AntiVPN.getInstance() + .getExecutor() + .log("Inserting %s new ranges into database...", rangesToInsert.size()); + + for (CIDRUtils cidr : rangesToInsert) { + try (var statement = + Query.prepare( + "insert into `whitelisted-ranges` (`cidr_string`, `ip_start`, `ip_end`) values (?, ?, ?)") + .append(cidr.getCidr()) + .append(cidr.getStartIpInt()) + .append(cidr.getEndIpInt())) { + statement.execute(); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not add cidr '" + cidr + "' to whitelist due to SQL error.", e); + } } - @Override - public boolean needsUpdate(VPNDatabase database) { - try (var statement = Query.prepare("select * from `database_version` where version = 2")) { - try(var set = statement.executeQuery()) { - return !set.next(); - } - } catch (SQLException e) { - return true; - } + AntiVPN.getInstance() + .getExecutor() + .log("Removing %s old ranges from database...", rangesToRemove.size()); + + for (BigInteger[] range : rangesToRemove) { + try (var statement = + Query.prepare("delete from `whitelisted-ranges` where `ip_start` = ? and `ip_end` = ?")) { + statement.append(range[0]).append(range[1]).execute(); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException( + "Could not remove cidr range '" + + range[0] + + ", " + + range[1] + + "' from whitelist due to SQL error.", + e); + } } + + AntiVPN.getInstance() + .getExecutor() + .log("Updating %s ranges to proper CIDR notation with the database", ipRanges.size()); + + for (CIDRUtils cidr : ipRanges) { + try (var statement = + Query.prepare( + "update `whitelisted-ranges` set `cidr_string` = ? where `ip_start` = ? and `ip_end` = ?")) { + statement + .append(cidr.getCidr()) + .append(cidr.getStartIpInt()) + .append(cidr.getEndIpInt()) + .execute(); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException( + "Could not update cidr '" + + cidr + + "' to proper CIDR notation in whitelist due to SQL error.", + e); + } + } + + try (var preparedStatement = + Query.prepare("INSERT INTO `database_version` (`version`) VALUES (?)") + .append(versionNumber())) { + preparedStatement.execute(); + } catch (SQLException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Could not update database version to 2 due to SQL error.", e); + } + } + + @Override + public int versionNumber() { + return 2; + } + + @Override + public boolean needsUpdate(VPNDatabase database) { + try (var statement = Query.prepare("select * from `database_version` where version = 2")) { + try (var set = statement.executeQuery()) { + return !set.next(); + } + } catch (SQLException e) { + return true; + } + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/MongoVPN.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/MongoVPN.java index bb2b806..3cff7a1 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/MongoVPN.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/MongoVPN.java @@ -16,8 +16,6 @@ package dev.brighten.antivpn.database.mongo; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; import com.mongodb.*; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoClients; @@ -30,251 +28,305 @@ import dev.brighten.antivpn.database.VPNDatabase; import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.web.objects.VPNResponse; -import org.bson.Document; -import org.bson.conversions.Bson; -import org.bson.types.Decimal128; - import java.math.BigDecimal; import java.net.UnknownHostException; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import org.bson.Document; +import org.bson.conversions.Bson; +import org.bson.types.Decimal128; public class MongoVPN implements VPNDatabase { - public MongoCollection settingsDocument; - MongoCollection cacheDocument; - private MongoClient client; - public MongoDatabase antivpnDatabase; + public MongoCollection settingsDocument; + MongoCollection cacheDocument; + private MongoClient client; + public MongoDatabase antivpnDatabase; - public MongoVPN() { - AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> { - if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return; + public MongoVPN() { + AntiVPN.getInstance() + .getExecutor() + .getThreadExecutor() + .scheduleAtFixedRate( + () -> { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return; - //Refreshing whitelisted players - AntiVPN.getInstance().getExecutor().getWhitelisted().clear(); - AntiVPN.getInstance().getExecutor().getWhitelisted() - .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted()); + // Refreshing whitelisted players + AntiVPN.getInstance().getExecutor().getWhitelisted().clear(); + AntiVPN.getInstance() + .getExecutor() + .getWhitelisted() + .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted()); - //Refreshing whitlisted IPs - AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear(); - AntiVPN.getInstance().getExecutor().getWhitelistedIps() - .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()); - }, 2, 30, TimeUnit.SECONDS); + // Refreshing whitlisted IPs + AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear(); + AntiVPN.getInstance() + .getExecutor() + .getWhitelistedIps() + .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()); + }, + 2, + 30, + TimeUnit.SECONDS); + } + + @Override + public Optional getStoredResponse(String ip) { + Document rdoc = cacheDocument.find(Filters.eq("ip", ip)).first(); + + if (rdoc != null) { + long lastUpdate = rdoc.get("lastAccess", 0L); + + if (System.currentTimeMillis() - lastUpdate > TimeUnit.HOURS.toMillis(1)) { + AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> deleteResponse(ip)); + return null; + } + + return Optional.of( + VPNResponse.builder() + .asn(rdoc.getString("asn")) + .ip(ip) + .countryName(rdoc.getString("countryName")) + .countryCode(rdoc.getString("countryCode")) + .city(rdoc.getString("city")) + .isp(rdoc.getString("isp")) + .method(rdoc.getString("method")) + .timeZone(rdoc.getString("timeZone")) + .proxy(rdoc.getBoolean("proxy")) + .cached(rdoc.getBoolean("cached")) + .success(true) + .latitude(rdoc.getDouble("latitude")) + .longitude(rdoc.getDouble("longitude")) + .lastAccess(rdoc.get("lastAccess", 0L)) + .build()); } - @Override - public Optional getStoredResponse(String ip) { - Document rdoc = cacheDocument.find(Filters.eq("ip", ip)).first(); + return Optional.empty(); + } - if(rdoc != null) { - long lastUpdate = rdoc.get("lastAccess", 0L); + @Override + public void cacheResponse(VPNResponse toCache) { + if (AntiVPN.getInstance().getVpnConfig().cachedResults()) { + Document rdoc = new Document("ip", toCache.getIp()); - if(System.currentTimeMillis() - lastUpdate > TimeUnit.HOURS.toMillis(1)) { - AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> deleteResponse(ip)); - return null; - } + rdoc.put("asn", toCache.getAsn()); + rdoc.put("countryName", toCache.getCountryName()); + rdoc.put("countryCode", toCache.getCountryCode()); + rdoc.put("city", toCache.getCity()); + rdoc.put("isp", toCache.getIsp()); + rdoc.put("method", toCache.getMethod()); + rdoc.put("timeZone", toCache.getTimeZone()); + rdoc.put("proxy", toCache.isProxy()); + rdoc.put("cached", toCache.isCached()); + rdoc.put("success", toCache.isSuccess()); + rdoc.put("latitude", toCache.getLatitude()); + rdoc.put("longitude", toCache.getLongitude()); + rdoc.put("lastAccess", System.currentTimeMillis()); - return Optional.of(VPNResponse.builder().asn(rdoc.getString("asn")).ip(ip) - .countryName(rdoc.getString("countryName")) - .countryCode(rdoc.getString("countryCode")) - .city(rdoc.getString("city")) - .isp(rdoc.getString("isp")) - .method(rdoc.getString("method")) - .timeZone(rdoc.getString("timeZone")) - .proxy(rdoc.getBoolean("proxy")) - .cached(rdoc.getBoolean("cached")) - .success(true) - .latitude(rdoc.getDouble("latitude")) - .longitude(rdoc.getDouble("longitude")) - .lastAccess(rdoc.get("lastAccess", 0L)) - .build()); - } - return Optional.empty(); - } - - @Override - public void cacheResponse(VPNResponse toCache) { - if(AntiVPN.getInstance().getVpnConfig().cachedResults()) { - Document rdoc = new Document("ip", toCache.getIp()); - - rdoc.put("asn", toCache.getAsn()); - rdoc.put("countryName", toCache.getCountryName()); - rdoc.put("countryCode", toCache.getCountryCode()); - rdoc.put("city", toCache.getCity()); - rdoc.put("isp", toCache.getIsp()); - rdoc.put("method", toCache.getMethod()); - rdoc.put("timeZone", toCache.getTimeZone()); - rdoc.put("proxy", toCache.isProxy()); - rdoc.put("cached", toCache.isCached()); - rdoc.put("success", toCache.isSuccess()); - rdoc.put("latitude", toCache.getLatitude()); - rdoc.put("longitude", toCache.getLongitude()); - rdoc.put("lastAccess", System.currentTimeMillis()); - - AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> { + AntiVPN.getInstance() + .getExecutor() + .getThreadExecutor() + .execute( + () -> { Bson update = new Document("$set", rdoc); - cacheDocument.updateOne(Filters.eq("ip", toCache.getIp()), update, - new UpdateOptions().upsert(true)); - }); - } + cacheDocument.updateOne( + Filters.eq("ip", toCache.getIp()), update, new UpdateOptions().upsert(true)); + }); } + } - @Override - public void deleteResponse(String ip) { - cacheDocument.deleteMany(Filters.eq("ip", ip)); + @Override + public void deleteResponse(String ip) { + cacheDocument.deleteMany(Filters.eq("ip", ip)); + } + + @Override + public boolean isWhitelisted(UUID uuid) { + return settingsDocument + .find( + Filters.and( + Filters.eq("setting", "whitelist"), Filters.eq("uuid", uuid.toString()))) + .first() + != null; + } + + @Override + public boolean isWhitelisted(String cidr) { + try { + return isWhitelisted(new CIDRUtils(cidr)); + } catch (UnknownHostException e) { + AntiVPN.getInstance().getExecutor().log("Failed to check whitelist for IP: " + cidr, e); + return false; } + } - @Override - public boolean isWhitelisted(UUID uuid) { - return settingsDocument - .find(Filters.and(Filters.eq("setting", "whitelist"), - Filters.eq("uuid", uuid.toString()))).first() != null; - } + @Override + public boolean isWhitelisted(CIDRUtils cidr) { + var start = new Decimal128(new BigDecimal(cidr.getStartIpInt())); + var end = new Decimal128(new BigDecimal(cidr.getEndIpInt())); + return settingsDocument + .find( + Filters.and( + Filters.eq("setting", "whitelist"), + Filters.lte("ip_start", start), + Filters.gte("ip_end", end))) + .first() + != null; + } - @Override - public boolean isWhitelisted(String cidr) { - try { - return isWhitelisted(new CIDRUtils(cidr)); - } catch (UnknownHostException e) { - AntiVPN.getInstance().getExecutor().log("Failed to check whitelist for IP: " + cidr, e); - return false; - } - } + @Override + public void addWhitelist(UUID uuid) { + Document wdoc = new Document("setting", "whitelist"); + wdoc.put("uuid", uuid.toString()); + AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid); + settingsDocument.insertOne(wdoc); + } - @Override - public boolean isWhitelisted(CIDRUtils cidr) { - var start = new Decimal128(new BigDecimal(cidr.getStartIpInt())); - var end = new Decimal128(new BigDecimal(cidr.getEndIpInt())); - return settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"), - Filters.lte("ip_start", start), Filters.gte("ip_end", end))).first() != null; - } + @Override + public void removeWhitelist(UUID uuid) { + AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid); + settingsDocument.deleteMany( + Filters.and(Filters.eq("setting", "whitelist"), Filters.eq("uuid", uuid.toString()))); + } - @Override - public void addWhitelist(UUID uuid) { - Document wdoc = new Document("setting", "whitelist"); - wdoc.put("uuid", uuid.toString()); - AntiVPN.getInstance().getExecutor().getWhitelisted().add(uuid); - settingsDocument.insertOne(wdoc); - } + @Override + public void addWhitelist(CIDRUtils cidr) { + Document doc = new Document("setting", "whitelist"); + doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))); + doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt()))); + doc.append("cidr_string", cidr.getCidr()); - @Override - public void removeWhitelist(UUID uuid) { - AntiVPN.getInstance().getExecutor().getWhitelisted().remove(uuid); - settingsDocument.deleteMany(Filters - .and( - Filters.eq("setting", "whitelist"), - Filters.eq("uuid", uuid.toString()))); - } + settingsDocument.insertOne(doc); + } - @Override - public void addWhitelist(CIDRUtils cidr) { - Document doc = new Document("setting", "whitelist"); - doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))); - doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt()))); - doc.append("cidr_string", cidr.getCidr()); + @Override + public void removeWhitelist(CIDRUtils cidr) { + settingsDocument.deleteMany( + Filters.and( + Filters.eq("setting", "whitelist"), + Filters.eq("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))), + Filters.eq("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt()))))); + } - settingsDocument.insertOne(doc); - } + @Override + public List getAllWhitelisted() { + List uuids = new ArrayList<>(); + settingsDocument + .find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("uuid"))) + .forEach( + (Consumer) doc -> uuids.add(UUID.fromString(doc.getString("uuid")))); + return uuids; + } - @Override - public void removeWhitelist(CIDRUtils cidr) { - settingsDocument.deleteMany(Filters - .and( - Filters.eq("setting", "whitelist"), - Filters.eq("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))), - Filters.eq("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt()))))); - } - - @Override - public List getAllWhitelisted() { - List uuids = new ArrayList<>(); - settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"), - Filters.exists("uuid"))) - .forEach((Consumer) doc -> uuids.add(UUID.fromString(doc.getString("uuid")))); - return uuids; - } - - @Override - public List getAllWhitelistedIps() { - List ips = new ArrayList<>(); - settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"), - Filters.exists("cidr_string"))).forEach((Consumer) doc -> { - try { - var cidr = new CIDRUtils(doc.getString("cidr_string")); - ips.add(cidr); - } catch (UnknownHostException e) { - AntiVPN.getInstance().getExecutor().logException("Could not format ip " + doc.getString("cidr_string") + " into a CIDR!", e); - } + @Override + public List getAllWhitelistedIps() { + List ips = new ArrayList<>(); + settingsDocument + .find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("cidr_string"))) + .forEach( + (Consumer) + doc -> { + try { + var cidr = new CIDRUtils(doc.getString("cidr_string")); + ips.add(cidr); + } catch (UnknownHostException e) { + AntiVPN.getInstance() + .getExecutor() + .logException( + "Could not format ip " + doc.getString("cidr_string") + " into a CIDR!", + e); + } }); - return ips; - } + return ips; + } - @Override - public void alertsState(UUID uuid, Consumer result) { - AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> result.accept(settingsDocument - .find(Filters.and(Filters.eq("setting", "alerts"), - Filters.eq("uuid", uuid.toString()))).first() != null)); - } + @Override + public void alertsState(UUID uuid, Consumer result) { + AntiVPN.getInstance() + .getExecutor() + .getThreadExecutor() + .execute( + () -> + result.accept( + settingsDocument + .find( + Filters.and( + Filters.eq("setting", "alerts"), + Filters.eq("uuid", uuid.toString()))) + .first() + != null)); + } - @Override - public void updateAlertsState(UUID uuid, boolean state) { - AntiVPN.getInstance().getExecutor().getThreadExecutor().execute(() -> { - settingsDocument.deleteMany(Filters.and(Filters.eq("setting", "alerts"), - Filters.eq("uuid", uuid.toString()))); - if(state) { + @Override + public void updateAlertsState(UUID uuid, boolean state) { + AntiVPN.getInstance() + .getExecutor() + .getThreadExecutor() + .execute( + () -> { + settingsDocument.deleteMany( + Filters.and( + Filters.eq("setting", "alerts"), Filters.eq("uuid", uuid.toString()))); + if (state) { Document adoc = new Document("setting", "alerts"); adoc.put("uuid", uuid.toString()); settingsDocument.insertOne(adoc); - } - }); + } + }); + } + + @Override + public void clearResponses() { + cacheDocument.deleteMany(Filters.exists("ip")); + } + + @Override + public void init() { + if (!AntiVPN.getInstance().getVpnConfig().mongoDatabaseURL().isEmpty()) { // URL + ConnectionString cs = + new ConnectionString(AntiVPN.getInstance().getVpnConfig().mongoDatabaseURL()); + MongoClientSettings settings = + MongoClientSettings.builder().applyConnectionString(cs).build(); + client = MongoClients.create(settings); + } else { + MongoClientSettings.Builder settingsBld = + MongoClientSettings.builder() + .readPreference(ReadPreference.nearest()) + .applyToClusterSettings( + builder -> + builder.hosts( + Collections.singletonList( + new ServerAddress( + AntiVPN.getInstance().getVpnConfig().getIp(), + AntiVPN.getInstance().getVpnConfig().getPort())))); + if (AntiVPN.getInstance().getVpnConfig().useDatabaseCreds()) { + settingsBld.credential( + MongoCredential.createCredential( + AntiVPN.getInstance().getVpnConfig().getUsername(), + AntiVPN.getInstance().getVpnConfig().getDatabaseName(), + AntiVPN.getInstance().getVpnConfig().getPassword().toCharArray())); + } + + client = MongoClients.create(settingsBld.build()); } + antivpnDatabase = client.getDatabase(AntiVPN.getInstance().getVpnConfig().getDatabaseName()); - @Override - public void clearResponses() { - cacheDocument.deleteMany(Filters.exists("ip")); + settingsDocument = antivpnDatabase.getCollection("settings"); + + cacheDocument = antivpnDatabase.getCollection("cache"); + + for (Version mongoDbVersion : Version.mongoDbVersions) { + if (mongoDbVersion.needsUpdate(this)) { + mongoDbVersion.update(this); + } } + } - @Override - public void init() { - if(!AntiVPN.getInstance().getVpnConfig().mongoDatabaseURL().isEmpty()) { //URL - ConnectionString cs = new ConnectionString(AntiVPN.getInstance().getVpnConfig().mongoDatabaseURL()); - MongoClientSettings settings = MongoClientSettings.builder().applyConnectionString(cs).build(); - client = MongoClients.create(settings); - } else { - MongoClientSettings.Builder settingsBld = MongoClientSettings.builder().readPreference(ReadPreference.nearest()) - .applyToClusterSettings(builder -> builder. - hosts(Collections.singletonList( - new ServerAddress( - AntiVPN.getInstance().getVpnConfig().getIp(), - AntiVPN.getInstance().getVpnConfig().getPort()) - ))); - if(AntiVPN.getInstance().getVpnConfig().useDatabaseCreds()) { - settingsBld.credential(MongoCredential - .createCredential(AntiVPN.getInstance().getVpnConfig().getUsername(), - AntiVPN.getInstance().getVpnConfig().getDatabaseName(), - AntiVPN.getInstance().getVpnConfig().getPassword().toCharArray())); - } - - client = MongoClients.create(settingsBld.build()); - } - antivpnDatabase = client.getDatabase(AntiVPN.getInstance().getVpnConfig().getDatabaseName()); - - settingsDocument = antivpnDatabase.getCollection("settings"); - - cacheDocument = antivpnDatabase.getCollection("cache"); - - for (Version mongoDbVersion : Version.mongoDbVersions) { - if(mongoDbVersion.needsUpdate(this)) { - mongoDbVersion.update(this); - } - } - } - - @Override - public void shutdown() { - settingsDocument = null; - cacheDocument = null; - client.close(); - } + @Override + public void shutdown() { + settingsDocument = null; + cacheDocument = null; + client.close(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/version/MongoFirst.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/version/MongoFirst.java index 8bf7203..a76113d 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/version/MongoFirst.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/version/MongoFirst.java @@ -26,27 +26,27 @@ import org.bson.Document; public class MongoFirst implements Version { - @Override - public void update(MongoVPN database) throws DatabaseException { - if(database.settingsDocument.listIndexes().first() == null) { - AntiVPN.getInstance().getExecutor().log("Created index for settings collection!"); - database.settingsDocument.createIndex(Indexes.ascending("ip")); - database.settingsDocument.createIndex(Indexes.ascending("setting")); - } - var versionCollect = database.antivpnDatabase.getCollection("version"); - - versionCollect.insertOne(new Document("version", versionNumber())); + @Override + public void update(MongoVPN database) throws DatabaseException { + if (database.settingsDocument.listIndexes().first() == null) { + AntiVPN.getInstance().getExecutor().log("Created index for settings collection!"); + database.settingsDocument.createIndex(Indexes.ascending("ip")); + database.settingsDocument.createIndex(Indexes.ascending("setting")); } + var versionCollect = database.antivpnDatabase.getCollection("version"); - @Override - public int versionNumber() { - return 0; - } + versionCollect.insertOne(new Document("version", versionNumber())); + } - @Override - public boolean needsUpdate(MongoVPN database) { - var versionCollect = database.antivpnDatabase.getCollection("version"); + @Override + public int versionNumber() { + return 0; + } - return versionCollect.find(Filters.eq("version", versionNumber())).first() == null; - } + @Override + public boolean needsUpdate(MongoVPN database) { + var versionCollect = database.antivpnDatabase.getCollection("version"); + + return versionCollect.find(Filters.eq("version", versionNumber())).first() == null; + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/version/MongoSecond.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/version/MongoSecond.java index c6faccc..c58e74e 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/version/MongoSecond.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/version/MongoSecond.java @@ -23,62 +23,68 @@ import dev.brighten.antivpn.database.DatabaseException; import dev.brighten.antivpn.database.mongo.MongoVPN; import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.utils.CIDRUtils; -import org.bson.Document; -import org.bson.types.Decimal128; - import java.math.BigDecimal; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import org.bson.Document; +import org.bson.types.Decimal128; public class MongoSecond implements Version { - @Override - public void update(MongoVPN database) throws DatabaseException { - List backup = new ArrayList<>(); - database.settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"), - Filters.exists("ip"))) - .forEach((Consumer) doc -> { - backup.add(new Document(doc)); + @Override + public void update(MongoVPN database) throws DatabaseException { + List backup = new ArrayList<>(); + database + .settingsDocument + .find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("ip"))) + .forEach( + (Consumer) + doc -> { + backup.add(new Document(doc)); - String ip = doc.getString("ip"); + String ip = doc.getString("ip"); - try { - var cidr = new CIDRUtils(ip + "/32"); + try { + var cidr = new CIDRUtils(ip + "/32"); - doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))); - doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt()))); - doc.append("cidr_string", cidr.toString()); - doc.remove("ip"); + doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))); + doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt()))); + doc.append("cidr_string", cidr.toString()); + doc.remove("ip"); - database.settingsDocument.replaceOne(Filters.eq("_id", doc.getObjectId("_id")), doc); - } catch (UnknownHostException e) { - rollback(backup, database); - throw new RuntimeException(e); - } + database.settingsDocument.replaceOne( + Filters.eq("_id", doc.getObjectId("_id")), doc); + } catch (UnknownHostException e) { + rollback(backup, database); + throw new RuntimeException(e); + } }); - database.settingsDocument.createIndex(Indexes.compoundIndex(Indexes.ascending("ip_start"), Indexes.ascending("ip_end"))); - database.settingsDocument.createIndex(Indexes.ascending("cidr_string")); - var versionCollect = database.antivpnDatabase.getCollection("version"); - versionCollect.insertOne(new Document("version", versionNumber())); - } + database.settingsDocument.createIndex( + Indexes.compoundIndex(Indexes.ascending("ip_start"), Indexes.ascending("ip_end"))); + database.settingsDocument.createIndex(Indexes.ascending("cidr_string")); + var versionCollect = database.antivpnDatabase.getCollection("version"); + versionCollect.insertOne(new Document("version", versionNumber())); + } - private void rollback(List toRollback, MongoVPN database) { - AntiVPN.getInstance().getExecutor().log("Rolling back to version 0..."); - toRollback.forEach(doc -> database.settingsDocument.replaceOne(Filters.eq("_id", doc.getObjectId("_id")), doc)); - toRollback.clear(); - } + private void rollback(List toRollback, MongoVPN database) { + AntiVPN.getInstance().getExecutor().log("Rolling back to version 0..."); + toRollback.forEach( + doc -> + database.settingsDocument.replaceOne(Filters.eq("_id", doc.getObjectId("_id")), doc)); + toRollback.clear(); + } - @Override - public int versionNumber() { - return 1; - } + @Override + public int versionNumber() { + return 1; + } - @Override - public boolean needsUpdate(MongoVPN database) { - var versionCollect = database.antivpnDatabase.getCollection("version"); + @Override + public boolean needsUpdate(MongoVPN database) { + var versionCollect = database.antivpnDatabase.getCollection("version"); - return versionCollect.find(Filters.eq("version", versionNumber())).first() == null; - } + return versionCollect.find(Filters.eq("version", versionNumber())).first() == null; + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/version/MongoThird.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/version/MongoThird.java index daec1fb..09d0235 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/version/MongoThird.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/mongo/version/MongoThird.java @@ -23,9 +23,6 @@ import dev.brighten.antivpn.database.mongo.MongoVPN; import dev.brighten.antivpn.database.version.Version; import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.utils.MiscUtils; -import org.bson.Document; -import org.bson.types.Decimal128; - import java.math.BigDecimal; import java.math.BigInteger; import java.net.UnknownHostException; @@ -33,76 +30,108 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.logging.Level; +import org.bson.Document; +import org.bson.types.Decimal128; -public class MongoThird implements Version { - @Override - public void update(MongoVPN database) throws DatabaseException { - List ipRanges = new ArrayList<>(); - List rangesToInsert = new ArrayList<>(); - List rangesToRemove = new ArrayList<>(); - database.settingsDocument.find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("cidr_string"))) - .forEach((Consumer) doc -> { - BigInteger start = doc.get("ip_start", Decimal128.class).bigDecimalValue().toBigInteger(); - BigInteger end = doc.get("ip_end", Decimal128.class).bigDecimalValue().toBigInteger(); +public class MongoThird implements Version { + @Override + public void update(MongoVPN database) throws DatabaseException { + List ipRanges = new ArrayList<>(); + List rangesToInsert = new ArrayList<>(); + List rangesToRemove = new ArrayList<>(); + database + .settingsDocument + .find(Filters.and(Filters.eq("setting", "whitelist"), Filters.exists("cidr_string"))) + .forEach( + (Consumer) + doc -> { + BigInteger start = + doc.get("ip_start", Decimal128.class).bigDecimalValue().toBigInteger(); + BigInteger end = + doc.get("ip_end", Decimal128.class).bigDecimalValue().toBigInteger(); - try { - var range = MiscUtils.rangeToCidrs(start, end); + try { + var range = MiscUtils.rangeToCidrs(start, end); - if(range.size() > 1) { - rangesToRemove.add(new BigInteger[]{start, end}); - rangesToInsert.addAll(range); - AntiVPN.getInstance().getExecutor().log(Level.WARNING, "Found multiple CIDR ranges for whitelist range for %s, %s!", start, end); - } else ipRanges.addAll(range); - } catch (UnknownHostException e) { - AntiVPN.getInstance().getExecutor().logException( - String.format("Could not convert ip range to CIDR! %s, %s", start, end), e); - } + if (range.size() > 1) { + rangesToRemove.add(new BigInteger[] {start, end}); + rangesToInsert.addAll(range); + AntiVPN.getInstance() + .getExecutor() + .log( + Level.WARNING, + "Found multiple CIDR ranges for whitelist range for %s, %s!", + start, + end); + } else ipRanges.addAll(range); + } catch (UnknownHostException e) { + AntiVPN.getInstance() + .getExecutor() + .logException( + String.format("Could not convert ip range to CIDR! %s, %s", start, end), + e); + } }); - if(!rangesToInsert.isEmpty()) { - AntiVPN.getInstance().getExecutor().log("Inserting %s new ranges into database...", rangesToInsert.size()); - var documentsToInsert = rangesToInsert.stream().map(cidr -> { - Document doc = new Document("setting", "whitelist"); - doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))); - doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt()))); - doc.append("cidr_string", cidr.getCidr()); + if (!rangesToInsert.isEmpty()) { + AntiVPN.getInstance() + .getExecutor() + .log("Inserting %s new ranges into database...", rangesToInsert.size()); + var documentsToInsert = + rangesToInsert.stream() + .map( + cidr -> { + Document doc = new Document("setting", "whitelist"); + doc.append("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))); + doc.append("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt()))); + doc.append("cidr_string", cidr.getCidr()); - return doc; - }).toList(); + return doc; + }) + .toList(); - database.settingsDocument.insertMany(documentsToInsert); - } - if(!rangesToRemove.isEmpty()) { - AntiVPN.getInstance().getExecutor().log("Removing %s old ranges from database...", rangesToRemove.size()); - rangesToRemove.forEach(range -> database.settingsDocument - .deleteMany(Filters.and( - Filters.gte("ip_start", new Decimal128(new BigDecimal(range[0]))), - Filters.lte("ip_end", new Decimal128(new BigDecimal(range[1])))))); - } - - if(!ipRanges.isEmpty()) { - AntiVPN.getInstance().getExecutor().log("Updating %s CIDRs in database with proper notation...", ipRanges.size()); - - ipRanges.forEach(cidr -> database.settingsDocument - .updateMany(Filters.and(Filters.eq("setting", "whitelist"), - Filters.eq("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))), - Filters.eq("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())))), - new Document("$set", new Document("cidr_string", cidr.getCidr())))); - } - - var versionCollect = database.antivpnDatabase.getCollection("version"); - versionCollect.insertOne(new Document("version", versionNumber())); + database.settingsDocument.insertMany(documentsToInsert); + } + if (!rangesToRemove.isEmpty()) { + AntiVPN.getInstance() + .getExecutor() + .log("Removing %s old ranges from database...", rangesToRemove.size()); + rangesToRemove.forEach( + range -> + database.settingsDocument.deleteMany( + Filters.and( + Filters.gte("ip_start", new Decimal128(new BigDecimal(range[0]))), + Filters.lte("ip_end", new Decimal128(new BigDecimal(range[1])))))); } - @Override - public int versionNumber() { - return 2; + if (!ipRanges.isEmpty()) { + AntiVPN.getInstance() + .getExecutor() + .log("Updating %s CIDRs in database with proper notation...", ipRanges.size()); + + ipRanges.forEach( + cidr -> + database.settingsDocument.updateMany( + Filters.and( + Filters.eq("setting", "whitelist"), + Filters.eq("ip_start", new Decimal128(new BigDecimal(cidr.getStartIpInt()))), + Filters.eq("ip_end", new Decimal128(new BigDecimal(cidr.getEndIpInt())))), + new Document("$set", new Document("cidr_string", cidr.getCidr())))); } - @Override - public boolean needsUpdate(MongoVPN database) { - var versionCollect = database.antivpnDatabase.getCollection("version"); + var versionCollect = database.antivpnDatabase.getCollection("version"); + versionCollect.insertOne(new Document("version", versionNumber())); + } - return versionCollect.find(Filters.eq("version", versionNumber())).first() == null; - } + @Override + public int versionNumber() { + return 2; + } + + @Override + public boolean needsUpdate(MongoVPN database) { + var versionCollect = database.antivpnDatabase.getCollection("version"); + + return versionCollect.find(Filters.eq("version", versionNumber())).first() == null; + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/MySqlVPN.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/MySqlVPN.java index 54ac223..28339ed 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/MySqlVPN.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/MySqlVPN.java @@ -20,45 +20,55 @@ import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.database.local.H2VPN; import dev.brighten.antivpn.database.sql.utils.MySQL; import dev.brighten.antivpn.database.version.Version; - import java.util.concurrent.TimeUnit; public class MySqlVPN extends H2VPN { - public MySqlVPN() { - AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(() -> { - if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) return; + public MySqlVPN() { + AntiVPN.getInstance() + .getExecutor() + .getThreadExecutor() + .scheduleAtFixedRate( + () -> { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed()) + return; - //Refreshing whitelisted players - AntiVPN.getInstance().getExecutor().getWhitelisted().clear(); - AntiVPN.getInstance().getExecutor().getWhitelisted() - .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted()); + // Refreshing whitelisted players + AntiVPN.getInstance().getExecutor().getWhitelisted().clear(); + AntiVPN.getInstance() + .getExecutor() + .getWhitelisted() + .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelisted()); - //Refreshing whitlisted IPs - AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear(); - AntiVPN.getInstance().getExecutor().getWhitelistedIps() - .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()); - }, 2, 30, TimeUnit.SECONDS); - } + // Refreshing whitlisted IPs + AntiVPN.getInstance().getExecutor().getWhitelistedIps().clear(); + AntiVPN.getInstance() + .getExecutor() + .getWhitelistedIps() + .addAll(AntiVPN.getInstance().getDatabase().getAllWhitelistedIps()); + }, + 2, + 30, + TimeUnit.SECONDS); + } - @Override - public void init() { - if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) - return; - AntiVPN.getInstance().getExecutor().log("Initializing MySQL..."); - MySQL.init(); + @Override + public void init() { + if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled()) return; + AntiVPN.getInstance().getExecutor().log("Initializing MySQL..."); + MySQL.init(); - AntiVPN.getInstance().getExecutor().log("Checking for updates..."); + AntiVPN.getInstance().getExecutor().log("Checking for updates..."); - //Running check for old table types to update - try { - for (Version version : Version.mysqlVersions) { - if(version.needsUpdate(this)) { - version.update(this); - } - } - } catch (Exception e) { - throw new RuntimeException("Could not complete version setup due to SQL error", e); + // Running check for old table types to update + try { + for (Version version : Version.mysqlVersions) { + if (version.needsUpdate(this)) { + version.update(this); } + } + } catch (Exception e) { + throw new RuntimeException("Could not complete version setup due to SQL error", e); } + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/ExecutableStatement.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/ExecutableStatement.java index 5b0a7e6..2e14b13 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/ExecutableStatement.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/ExecutableStatement.java @@ -16,132 +16,130 @@ package dev.brighten.antivpn.database.sql.utils; +import java.sql.*; +import java.util.UUID; import lombok.Getter; import lombok.SneakyThrows; -import java.sql.*; -import java.util.UUID; - public class ExecutableStatement implements AutoCloseable { - @Getter - private final PreparedStatement statement; - private int pos = 1; + @Getter private final PreparedStatement statement; + private int pos = 1; - public ExecutableStatement(PreparedStatement statement) { - this.statement = statement; - } + public ExecutableStatement(PreparedStatement statement) { + this.statement = statement; + } - public int execute() throws SQLException { - return statement.executeUpdate(); - } + public int execute() throws SQLException { + return statement.executeUpdate(); + } - public void execute(ResultSetIterator iterator) throws SQLException { - try(var rs = statement.executeQuery()) { - while (rs.next()) iterator.next(rs); - } + public void execute(ResultSetIterator iterator) throws SQLException { + try (var rs = statement.executeQuery()) { + while (rs.next()) iterator.next(rs); } + } - public int[] executeBatch() throws SQLException { - return statement.executeBatch(); - } + public int[] executeBatch() throws SQLException { + return statement.executeBatch(); + } - public ResultSet executeQuery() throws SQLException { - return statement.executeQuery(); - } + public ResultSet executeQuery() throws SQLException { + return statement.executeQuery(); + } - @SneakyThrows - public ExecutableStatement append(Object obj) { - statement.setObject(pos++, obj); - return this; - } + @SneakyThrows + public ExecutableStatement append(Object obj) { + statement.setObject(pos++, obj); + return this; + } - @SneakyThrows - public ExecutableStatement append(String obj) { - statement.setString(pos++, obj); - return this; - } + @SneakyThrows + public ExecutableStatement append(String obj) { + statement.setString(pos++, obj); + return this; + } - @SneakyThrows - public ExecutableStatement append(UUID uuid) { - if (uuid != null) statement.setString(pos++, uuid.toString().replace("-", "")); - else statement.setString(pos++, null); - return this; - } + @SneakyThrows + public ExecutableStatement append(UUID uuid) { + if (uuid != null) statement.setString(pos++, uuid.toString().replace("-", "")); + else statement.setString(pos++, null); + return this; + } - @SneakyThrows - public ExecutableStatement append(Array obj) { - statement.setArray(pos++, obj); - return this; - } + @SneakyThrows + public ExecutableStatement append(Array obj) { + statement.setArray(pos++, obj); + return this; + } - @SneakyThrows - public ExecutableStatement append(Integer obj) { - statement.setInt(pos++, obj); - return this; - } + @SneakyThrows + public ExecutableStatement append(Integer obj) { + statement.setInt(pos++, obj); + return this; + } - @SneakyThrows - public ExecutableStatement append(Short obj) { - statement.setShort(pos++, obj); - return this; - } + @SneakyThrows + public ExecutableStatement append(Short obj) { + statement.setShort(pos++, obj); + return this; + } - @SneakyThrows - public ExecutableStatement append(Long obj) { - statement.setLong(pos++, obj); - return this; - } + @SneakyThrows + public ExecutableStatement append(Long obj) { + statement.setLong(pos++, obj); + return this; + } - @SneakyThrows - public ExecutableStatement append(Float obj) { - statement.setFloat(pos++, obj); - return this; - } + @SneakyThrows + public ExecutableStatement append(Float obj) { + statement.setFloat(pos++, obj); + return this; + } - @SneakyThrows - public ExecutableStatement append(Double obj) { - statement.setDouble(pos++, obj); - return this; - } + @SneakyThrows + public ExecutableStatement append(Double obj) { + statement.setDouble(pos++, obj); + return this; + } - @SneakyThrows - public ExecutableStatement append(Date obj) { - statement.setDate(pos++, obj); - return this; - } + @SneakyThrows + public ExecutableStatement append(Date obj) { + statement.setDate(pos++, obj); + return this; + } - @SneakyThrows - public ExecutableStatement append(Timestamp obj) { - statement.setTimestamp(pos++, obj); - return this; - } + @SneakyThrows + public ExecutableStatement append(Timestamp obj) { + statement.setTimestamp(pos++, obj); + return this; + } - @SneakyThrows - public ExecutableStatement append(Time obj) { - statement.setTime(pos++, obj); - return this; - } + @SneakyThrows + public ExecutableStatement append(Time obj) { + statement.setTime(pos++, obj); + return this; + } - @SneakyThrows - public ExecutableStatement append(Blob obj) { - statement.setBlob(pos++, obj); - return this; - } + @SneakyThrows + public ExecutableStatement append(Blob obj) { + statement.setBlob(pos++, obj); + return this; + } - @SneakyThrows - public ExecutableStatement append(byte[] obj) { - statement.setBytes(pos++, obj); - return this; - } + @SneakyThrows + public ExecutableStatement append(byte[] obj) { + statement.setBytes(pos++, obj); + return this; + } - @SneakyThrows - public ExecutableStatement addBatch() { - statement.addBatch(); - return this; - } + @SneakyThrows + public ExecutableStatement addBatch() { + statement.addBatch(); + return this; + } - @Override - public void close() throws SQLException { - statement.close(); - } + @Override + public void close() throws SQLException { + statement.close(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java index 662ef70..0f64a2e 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/MySQL.java @@ -18,9 +18,6 @@ package dev.brighten.antivpn.database.sql.utils; import com.mysql.cj.jdbc.Driver; import dev.brighten.antivpn.AntiVPN; -import org.h2.jdbc.JdbcSQLFeatureNotSupportedException; -import org.h2.jdbc.JdbcSQLNonTransientConnectionException; - import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -29,167 +26,194 @@ import java.sql.SQLException; import java.sql.SQLSyntaxErrorException; import java.util.Properties; import java.util.logging.Level; +import org.h2.jdbc.JdbcSQLFeatureNotSupportedException; +import org.h2.jdbc.JdbcSQLNonTransientConnectionException; public class MySQL { - private static Connection conn; + private static Connection conn; - public static void init() { - try { - if (conn == null || conn.isClosed()) { - String url = "jdbc:mysql://" + AntiVPN.getInstance().getVpnConfig().getIp() - + ":" + AntiVPN.getInstance().getVpnConfig().getPort() - + "/?useSSL=true&autoReconnect=true"; - Properties properties = new Properties(); - properties.setProperty("user", AntiVPN.getInstance().getVpnConfig().getUsername()); - properties.setProperty("password", AntiVPN.getInstance().getVpnConfig().getPassword()); + public static void init() { + try { + if (conn == null || conn.isClosed()) { + String url = + "jdbc:mysql://" + + AntiVPN.getInstance().getVpnConfig().getIp() + + ":" + + AntiVPN.getInstance().getVpnConfig().getPort() + + "/?useSSL=true&autoReconnect=true"; + Properties properties = new Properties(); + properties.setProperty("user", AntiVPN.getInstance().getVpnConfig().getUsername()); + properties.setProperty("password", AntiVPN.getInstance().getVpnConfig().getPassword()); - conn = new Driver().connect(url, properties); - if (conn == null) { - throw new SQLException("MySQL driver did not accept URL: " + url); - } - conn.setAutoCommit(true); - Query.use(conn); - String databaseName = AntiVPN.getInstance().getVpnConfig().getDatabaseName(); - - try { - Query.prepare("CREATE DATABASE IF NOT EXISTS `" + databaseName + "`").execute(); - } catch (SQLException ex) { - if (!isDatabaseCreationPermissionIssue(ex)) { - throw ex; - } - - AntiVPN.getInstance().getExecutor().log( - "No permission to create MySQL database `" + databaseName - + "`. Attempting to use the existing database instead."); - } - - Query.prepare("USE `" + databaseName + "`").execute(); - AntiVPN.getInstance().getExecutor().log("Connection to MySQL has been established."); - } - } catch (Exception e) { - AntiVPN.getInstance().getExecutor().logException("Failed to load mysql: " + e.getMessage(), e); - throw new RuntimeException("Could not initialize MySQL connection", e); + conn = new Driver().connect(url, properties); + if (conn == null) { + throw new SQLException("MySQL driver did not accept URL: " + url); } - } + conn.setAutoCommit(true); + Query.use(conn); + String databaseName = AntiVPN.getInstance().getVpnConfig().getDatabaseName(); - private static boolean isDatabaseCreationPermissionIssue(SQLException ex) { - return ex instanceof SQLSyntaxErrorException - && ex.getMessage() != null - && ex.getMessage().contains("Access denied"); - } - - public static void initH2() { - initH2(true); - } - - private static void initH2(boolean allowRetry) { - File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases"); - if (!dataFolder.exists() && dataFolder.mkdirs()) { - AntiVPN.getInstance().getExecutor().log("Created database directory"); - } - - File dbFile = new File(dataFolder, "database.mv.db"); - - File databaseFile = new File(dataFolder, "database"); try { - conn = new NonClosableConnection(new org.h2.jdbc.JdbcConnection("jdbc:h2:file:" + - databaseFile.getAbsolutePath(), - new Properties(), AntiVPN.getInstance().getVpnConfig().getUsername(), - AntiVPN.getInstance().getVpnConfig().getPassword(), false)); - conn.setAutoCommit(true); - Query.use(conn); - AntiVPN.getInstance().getExecutor().log("Connection to H2 has been established."); + Query.prepare("CREATE DATABASE IF NOT EXISTS `" + databaseName + "`").execute(); } catch (SQLException ex) { - AntiVPN.getInstance().getExecutor().logException("H2 exception on initialize", ex); - if(ex instanceof JdbcSQLFeatureNotSupportedException - || ex instanceof JdbcSQLNonTransientConnectionException) { - AntiVPN.getInstance().getExecutor() - .log("H2 database file is incompatible with this version of AntiVPN. " + - "Backing up old database file..."); - shutdown(); - if (allowRetry && backupOldDB(dbFile, dataFolder)) { - initH2(false); - } else { - AntiVPN.getInstance().getExecutor().log( - "Could not back up and remove the incompatible H2 database file automatically."); - } - } else { - AntiVPN.getInstance().getExecutor().logException("Failed to load H2 database: " + ex.getCause().toString(), ex); - } - } catch (Exception e) { - AntiVPN.getInstance().getExecutor().logException("Failed to load H2 database: " + e.getMessage(), e); - AntiVPN.getInstance().getExecutor().log(Level.INFO, "TIP: Try deleting the plugin folder and restarting your server!"); + if (!isDatabaseCreationPermissionIssue(ex)) { + throw ex; + } + + AntiVPN.getInstance() + .getExecutor() + .log( + "No permission to create MySQL database `" + + databaseName + + "`. Attempting to use the existing database instead."); } + + Query.prepare("USE `" + databaseName + "`").execute(); + AntiVPN.getInstance().getExecutor().log("Connection to MySQL has been established."); + } + } catch (Exception e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Failed to load mysql: " + e.getMessage(), e); + throw new RuntimeException("Could not initialize MySQL connection", e); + } + } + + private static boolean isDatabaseCreationPermissionIssue(SQLException ex) { + return ex instanceof SQLSyntaxErrorException + && ex.getMessage() != null + && ex.getMessage().contains("Access denied"); + } + + public static void initH2() { + initH2(true); + } + + private static void initH2(boolean allowRetry) { + File dataFolder = new File(AntiVPN.getInstance().getPluginFolder(), "databases"); + if (!dataFolder.exists() && dataFolder.mkdirs()) { + AntiVPN.getInstance().getExecutor().log("Created database directory"); } - public static boolean backupOldDB(File dbFile, File dataFolder) { - if (!dbFile.exists()) { - return true; + File dbFile = new File(dataFolder, "database.mv.db"); + + File databaseFile = new File(dataFolder, "database"); + try { + conn = + new NonClosableConnection( + new org.h2.jdbc.JdbcConnection( + "jdbc:h2:file:" + databaseFile.getAbsolutePath(), + new Properties(), + AntiVPN.getInstance().getVpnConfig().getUsername(), + AntiVPN.getInstance().getVpnConfig().getPassword(), + false)); + conn.setAutoCommit(true); + Query.use(conn); + AntiVPN.getInstance().getExecutor().log("Connection to H2 has been established."); + } catch (SQLException ex) { + AntiVPN.getInstance().getExecutor().logException("H2 exception on initialize", ex); + if (ex instanceof JdbcSQLFeatureNotSupportedException + || ex instanceof JdbcSQLNonTransientConnectionException) { + AntiVPN.getInstance() + .getExecutor() + .log( + "H2 database file is incompatible with this version of AntiVPN. " + + "Backing up old database file..."); + shutdown(); + if (allowRetry && backupOldDB(dbFile, dataFolder)) { + initH2(false); + } else { + AntiVPN.getInstance() + .getExecutor() + .log("Could not back up and remove the incompatible H2 database file automatically."); } + } else { + AntiVPN.getInstance() + .getExecutor() + .logException("Failed to load H2 database: " + ex.getCause().toString(), ex); + } + } catch (Exception e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Failed to load H2 database: " + e.getMessage(), e); + AntiVPN.getInstance() + .getExecutor() + .log(Level.INFO, "TIP: Try deleting the plugin folder and restarting your server!"); + } + } - if (!dbFile.isFile()) { - AntiVPN.getInstance().getExecutor().log("Skipping backup for non-file path: " + dbFile.getAbsolutePath()); - return false; - } + public static boolean backupOldDB(File dbFile, File dataFolder) { + if (!dbFile.exists()) { + return true; + } - try { - File backupDir = new File(dataFolder, "backups"); - if(backupDir.mkdirs()) { - AntiVPN.getInstance().getExecutor().log("Created backup directory"); - } else if (backupDir.exists()) { - AntiVPN.getInstance().getExecutor().log("Backup directory already exists"); - } else { - AntiVPN.getInstance().getExecutor().log("Could not create backup directory"); - return false; - } - File backupFile = new File(backupDir, dbFile.getName() + ".backup_" + System.currentTimeMillis()); - Files.copy(dbFile.toPath(), backupFile.toPath()); - - if (!dbFile.delete()) { - dbFile.deleteOnExit(); - AntiVPN.getInstance().getExecutor().log("Could not delete database file - will try again on shutdown"); - return false; - } - - AntiVPN.getInstance().getExecutor().log("Successfully deleted incompatible database file"); - return true; - } catch (IOException ex) { - AntiVPN.getInstance().getExecutor().logException("Failed to handle database file", ex); - } + if (!dbFile.isFile()) { + AntiVPN.getInstance() + .getExecutor() + .log("Skipping backup for non-file path: " + dbFile.getAbsolutePath()); + return false; + } + try { + File backupDir = new File(dataFolder, "backups"); + if (backupDir.mkdirs()) { + AntiVPN.getInstance().getExecutor().log("Created backup directory"); + } else if (backupDir.exists()) { + AntiVPN.getInstance().getExecutor().log("Backup directory already exists"); + } else { + AntiVPN.getInstance().getExecutor().log("Could not create backup directory"); return false; + } + File backupFile = + new File(backupDir, dbFile.getName() + ".backup_" + System.currentTimeMillis()); + Files.copy(dbFile.toPath(), backupFile.toPath()); + + if (!dbFile.delete()) { + dbFile.deleteOnExit(); + AntiVPN.getInstance() + .getExecutor() + .log("Could not delete database file - will try again on shutdown"); + return false; + } + + AntiVPN.getInstance().getExecutor().log("Successfully deleted incompatible database file"); + return true; + } catch (IOException ex) { + AntiVPN.getInstance().getExecutor().logException("Failed to handle database file", ex); } - public static void use() { - try { - init(); - } catch (Exception e) { - AntiVPN.getInstance().getExecutor().logException(e); - } - } + return false; + } - public static void shutdown() { - try { - if(conn != null && !conn.isClosed()) { - if(conn instanceof NonClosableConnection) { - ((NonClosableConnection)conn).shutdown(); - } else conn.close(); - conn = null; - } - } catch (Exception e) { - AntiVPN.getInstance().getExecutor().logException(e); - } + public static void use() { + try { + init(); + } catch (Exception e) { + AntiVPN.getInstance().getExecutor().logException(e); } + } - public static boolean isClosed() { - if(conn == null) - return true; - - try { - return conn.isClosed(); - } catch (SQLException e) { - AntiVPN.getInstance().getExecutor().logException(e); - return true; - } + public static void shutdown() { + try { + if (conn != null && !conn.isClosed()) { + if (conn instanceof NonClosableConnection) { + ((NonClosableConnection) conn).shutdown(); + } else conn.close(); + conn = null; + } + } catch (Exception e) { + AntiVPN.getInstance().getExecutor().logException(e); } + } + + public static boolean isClosed() { + if (conn == null) return true; + + try { + return conn.isClosed(); + } catch (SQLException e) { + AntiVPN.getInstance().getExecutor().logException(e); + return true; + } + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/NonClosableConnection.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/NonClosableConnection.java index 724d5c2..1d76a5a 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/NonClosableConnection.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/NonClosableConnection.java @@ -25,89 +25,299 @@ import java.util.concurrent.Executor; * A wrapper around a {@link Connection} which blocks usage of the default {@link #close()} method. */ public class NonClosableConnection implements Connection { - private final Connection delegate; + private final Connection delegate; - public NonClosableConnection(Connection delegate) { - this.delegate = delegate; + public NonClosableConnection(Connection delegate) { + this.delegate = delegate; + } + + /** Actually {@link #close() closes} the underlying connection. */ + public final void shutdown() throws SQLException { + this.delegate.close(); + } + + @Override + public final void close() throws SQLException { + // do nothing + } + + @Override + public final boolean isWrapperFor(Class iface) throws SQLException { + return iface.isInstance(this.delegate) || this.delegate.isWrapperFor(iface); + } + + @SuppressWarnings("unchecked") + @Override + public final T unwrap(Class iface) throws SQLException { + if (iface.isInstance(this.delegate)) { + return (T) this.delegate; } + return this.delegate.unwrap(iface); + } - /** - * Actually {@link #close() closes} the underlying connection. - */ - public final void shutdown() throws SQLException { - this.delegate.close(); - } + // Forward to the delegate connection + @Override + public Statement createStatement() throws SQLException { + return this.delegate.createStatement(); + } - @Override - public final void close() throws SQLException { - // do nothing - } + @Override + public PreparedStatement prepareStatement(String sql) throws SQLException { + return this.delegate.prepareStatement(sql); + } - @Override - public final boolean isWrapperFor(Class iface) throws SQLException { - return iface.isInstance(this.delegate) || this.delegate.isWrapperFor(iface); - } + @Override + public CallableStatement prepareCall(String sql) throws SQLException { + return this.delegate.prepareCall(sql); + } - @SuppressWarnings("unchecked") - @Override - public final T unwrap(Class iface) throws SQLException { - if (iface.isInstance(this.delegate)) { - return (T) this.delegate; - } - return this.delegate.unwrap(iface); - } + @Override + public String nativeSQL(String sql) throws SQLException { + return this.delegate.nativeSQL(sql); + } - // Forward to the delegate connection - @Override public Statement createStatement() throws SQLException { return this.delegate.createStatement(); } - @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return this.delegate.prepareStatement(sql); } - @Override public CallableStatement prepareCall(String sql) throws SQLException { return this.delegate.prepareCall(sql); } - @Override public String nativeSQL(String sql) throws SQLException { return this.delegate.nativeSQL(sql); } - @Override public void setAutoCommit(boolean autoCommit) throws SQLException { this.delegate.setAutoCommit(autoCommit); } - @Override public boolean getAutoCommit() throws SQLException { return this.delegate.getAutoCommit(); } - @Override public void commit() throws SQLException { this.delegate.commit(); } - @Override public void rollback() throws SQLException { this.delegate.rollback(); } - @Override public boolean isClosed() throws SQLException { return this.delegate.isClosed(); } - @Override public DatabaseMetaData getMetaData() throws SQLException { return this.delegate.getMetaData(); } - @Override public void setReadOnly(boolean readOnly) throws SQLException { this.delegate.setReadOnly(readOnly); } - @Override public boolean isReadOnly() throws SQLException { return this.delegate.isReadOnly(); } - @Override public void setCatalog(String catalog) throws SQLException { this.delegate.setCatalog(catalog); } - @Override public String getCatalog() throws SQLException { return this.delegate.getCatalog(); } - @Override public void setTransactionIsolation(int level) throws SQLException { this.delegate.setTransactionIsolation(level); } - @Override public int getTransactionIsolation() throws SQLException { return this.delegate.getTransactionIsolation(); } - @Override public SQLWarning getWarnings() throws SQLException { return this.delegate.getWarnings(); } - @Override public void clearWarnings() throws SQLException { this.delegate.clearWarnings(); } - @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { return this.delegate.createStatement(resultSetType, resultSetConcurrency); } - @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return this.delegate.prepareStatement(sql, resultSetType, resultSetConcurrency); } - @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return this.delegate.prepareCall(sql, resultSetType, resultSetConcurrency); } - @Override public Map> getTypeMap() throws SQLException { return this.delegate.getTypeMap(); } - @Override public void setTypeMap(Map> map) throws SQLException { this.delegate.setTypeMap(map); } - @Override public void setHoldability(int holdability) throws SQLException { this.delegate.setHoldability(holdability); } - @Override public int getHoldability() throws SQLException { return this.delegate.getHoldability(); } - @Override public Savepoint setSavepoint() throws SQLException { return this.delegate.setSavepoint(); } - @Override public Savepoint setSavepoint(String name) throws SQLException { return this.delegate.setSavepoint(name); } - @Override public void rollback(Savepoint savepoint) throws SQLException { this.delegate.rollback(savepoint); } - @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { this.delegate.releaseSavepoint(savepoint); } - @Override public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return this.delegate.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); } - @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return this.delegate.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); } - @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return this.delegate.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); } - @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { return this.delegate.prepareStatement(sql, autoGeneratedKeys); } - @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { return this.delegate.prepareStatement(sql, columnIndexes); } - @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { return this.delegate.prepareStatement(sql, columnNames); } - @Override public Clob createClob() throws SQLException { return this.delegate.createClob(); } - @Override public Blob createBlob() throws SQLException { return this.delegate.createBlob(); } - @Override public NClob createNClob() throws SQLException { return this.delegate.createNClob(); } - @Override public SQLXML createSQLXML() throws SQLException { return this.delegate.createSQLXML(); } - @Override public boolean isValid(int timeout) throws SQLException { return this.delegate.isValid(timeout); } - @Override public void setClientInfo(String name, String value) throws SQLClientInfoException { this.delegate.setClientInfo(name, value); } - @Override public void setClientInfo(Properties properties) throws SQLClientInfoException { this.delegate.setClientInfo(properties); } - @Override public String getClientInfo(String name) throws SQLException { return this.delegate.getClientInfo(name); } - @Override public Properties getClientInfo() throws SQLException { return this.delegate.getClientInfo(); } - @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { return this.delegate.createArrayOf(typeName, elements); } - @Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { return this.delegate.createStruct(typeName, attributes); } - @Override public void setSchema(String schema) throws SQLException { this.delegate.setSchema(schema); } - @Override public String getSchema() throws SQLException { return this.delegate.getSchema(); } - @Override public void abort(Executor executor) throws SQLException { this.delegate.abort(executor); } - @Override public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { this.delegate.setNetworkTimeout(executor, milliseconds); } - @Override public int getNetworkTimeout() throws SQLException { return this.delegate.getNetworkTimeout(); } + @Override + public void setAutoCommit(boolean autoCommit) throws SQLException { + this.delegate.setAutoCommit(autoCommit); + } + @Override + public boolean getAutoCommit() throws SQLException { + return this.delegate.getAutoCommit(); + } + + @Override + public void commit() throws SQLException { + this.delegate.commit(); + } + + @Override + public void rollback() throws SQLException { + this.delegate.rollback(); + } + + @Override + public boolean isClosed() throws SQLException { + return this.delegate.isClosed(); + } + + @Override + public DatabaseMetaData getMetaData() throws SQLException { + return this.delegate.getMetaData(); + } + + @Override + public void setReadOnly(boolean readOnly) throws SQLException { + this.delegate.setReadOnly(readOnly); + } + + @Override + public boolean isReadOnly() throws SQLException { + return this.delegate.isReadOnly(); + } + + @Override + public void setCatalog(String catalog) throws SQLException { + this.delegate.setCatalog(catalog); + } + + @Override + public String getCatalog() throws SQLException { + return this.delegate.getCatalog(); + } + + @Override + public void setTransactionIsolation(int level) throws SQLException { + this.delegate.setTransactionIsolation(level); + } + + @Override + public int getTransactionIsolation() throws SQLException { + return this.delegate.getTransactionIsolation(); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return this.delegate.getWarnings(); + } + + @Override + public void clearWarnings() throws SQLException { + this.delegate.clearWarnings(); + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency) + throws SQLException { + return this.delegate.createStatement(resultSetType, resultSetConcurrency); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException { + return this.delegate.prepareStatement(sql, resultSetType, resultSetConcurrency); + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) + throws SQLException { + return this.delegate.prepareCall(sql, resultSetType, resultSetConcurrency); + } + + @Override + public Map> getTypeMap() throws SQLException { + return this.delegate.getTypeMap(); + } + + @Override + public void setTypeMap(Map> map) throws SQLException { + this.delegate.setTypeMap(map); + } + + @Override + public void setHoldability(int holdability) throws SQLException { + this.delegate.setHoldability(holdability); + } + + @Override + public int getHoldability() throws SQLException { + return this.delegate.getHoldability(); + } + + @Override + public Savepoint setSavepoint() throws SQLException { + return this.delegate.setSavepoint(); + } + + @Override + public Savepoint setSavepoint(String name) throws SQLException { + return this.delegate.setSavepoint(name); + } + + @Override + public void rollback(Savepoint savepoint) throws SQLException { + this.delegate.rollback(savepoint); + } + + @Override + public void releaseSavepoint(Savepoint savepoint) throws SQLException { + this.delegate.releaseSavepoint(savepoint); + } + + @Override + public Statement createStatement( + int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + return this.delegate.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); + } + + @Override + public PreparedStatement prepareStatement( + String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException { + return this.delegate.prepareStatement( + sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + @Override + public CallableStatement prepareCall( + String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) + throws SQLException { + return this.delegate.prepareCall( + sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + @Override + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { + return this.delegate.prepareStatement(sql, autoGeneratedKeys); + } + + @Override + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { + return this.delegate.prepareStatement(sql, columnIndexes); + } + + @Override + public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { + return this.delegate.prepareStatement(sql, columnNames); + } + + @Override + public Clob createClob() throws SQLException { + return this.delegate.createClob(); + } + + @Override + public Blob createBlob() throws SQLException { + return this.delegate.createBlob(); + } + + @Override + public NClob createNClob() throws SQLException { + return this.delegate.createNClob(); + } + + @Override + public SQLXML createSQLXML() throws SQLException { + return this.delegate.createSQLXML(); + } + + @Override + public boolean isValid(int timeout) throws SQLException { + return this.delegate.isValid(timeout); + } + + @Override + public void setClientInfo(String name, String value) throws SQLClientInfoException { + this.delegate.setClientInfo(name, value); + } + + @Override + public void setClientInfo(Properties properties) throws SQLClientInfoException { + this.delegate.setClientInfo(properties); + } + + @Override + public String getClientInfo(String name) throws SQLException { + return this.delegate.getClientInfo(name); + } + + @Override + public Properties getClientInfo() throws SQLException { + return this.delegate.getClientInfo(); + } + + @Override + public Array createArrayOf(String typeName, Object[] elements) throws SQLException { + return this.delegate.createArrayOf(typeName, elements); + } + + @Override + public Struct createStruct(String typeName, Object[] attributes) throws SQLException { + return this.delegate.createStruct(typeName, attributes); + } + + @Override + public void setSchema(String schema) throws SQLException { + this.delegate.setSchema(schema); + } + + @Override + public String getSchema() throws SQLException { + return this.delegate.getSchema(); + } + + @Override + public void abort(Executor executor) throws SQLException { + this.delegate.abort(executor); + } + + @Override + public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { + this.delegate.setNetworkTimeout(executor, milliseconds); + } + + @Override + public int getNetworkTimeout() throws SQLException { + return this.delegate.getNetworkTimeout(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/Query.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/Query.java index 8c790e7..8700166 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/Query.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/Query.java @@ -16,24 +16,20 @@ package dev.brighten.antivpn.database.sql.utils; +import java.sql.Connection; +import java.sql.SQLException; import lombok.Getter; import org.intellij.lang.annotations.Language; -import java.sql.Connection; -import java.sql.SQLException; - public class Query { - @Getter - private static Connection conn; - - public static void use(Connection conn) { - Query.conn = conn; - } - - @SuppressWarnings("SqlSourceToSinkFlow") - public static ExecutableStatement prepare(@Language("SQL") String sql) throws SQLException { - return new ExecutableStatement(conn.prepareStatement(sql)); - } + @Getter private static Connection conn; + public static void use(Connection conn) { + Query.conn = conn; + } + @SuppressWarnings("SqlSourceToSinkFlow") + public static ExecutableStatement prepare(@Language("SQL") String sql) throws SQLException { + return new ExecutableStatement(conn.prepareStatement(sql)); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/ResultSetIterator.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/ResultSetIterator.java index fa16de4..4747f71 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/ResultSetIterator.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/utils/ResultSetIterator.java @@ -20,5 +20,5 @@ import java.sql.ResultSet; import java.sql.SQLException; public interface ResultSetIterator { - void next(ResultSet rs) throws SQLException; + void next(ResultSet rs) throws SQLException; } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/version/MySQLFirst.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/version/MySQLFirst.java index 9f40d48..e16c753 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/version/MySQLFirst.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/sql/version/MySQLFirst.java @@ -21,31 +21,34 @@ import dev.brighten.antivpn.database.DatabaseException; import dev.brighten.antivpn.database.VPNDatabase; import dev.brighten.antivpn.database.local.version.First; import dev.brighten.antivpn.database.sql.utils.Query; - import java.sql.SQLException; public class MySQLFirst extends First { - @Override - public void update(VPNDatabase database) throws DatabaseException { - try(var statement = Query.prepare("select `DATA_TYPE` from INFORMATION_SCHEMA.COLUMNS " + - "WHERE table_name = 'responses' AND COLUMN_NAME = 'isp';")) { - statement.execute(set -> { - if(set.getObject("DATA_TYPE").toString().contains("varchar")) { - AntiVPN.getInstance().getExecutor().log("Using old database format for storing responses! " + - "Dropping table and creating a new one..."); - try(var state = Query.prepare("drop table `responses`")) { - if(state.execute() > 0) { - AntiVPN.getInstance().getExecutor().log("Successfully dropped table!"); - } - } - - + @Override + public void update(VPNDatabase database) throws DatabaseException { + try (var statement = + Query.prepare( + "select `DATA_TYPE` from INFORMATION_SCHEMA.COLUMNS " + + "WHERE table_name = 'responses' AND COLUMN_NAME = 'isp';")) { + statement.execute( + set -> { + if (set.getObject("DATA_TYPE").toString().contains("varchar")) { + AntiVPN.getInstance() + .getExecutor() + .log( + "Using old database format for storing responses! " + + "Dropping table and creating a new one..."); + try (var state = Query.prepare("drop table `responses`")) { + if (state.execute() > 0) { + AntiVPN.getInstance().getExecutor().log("Successfully dropped table!"); } - }); - } catch (SQLException e) { - throw new DatabaseException("Could not update MySQL database", e); - } - super.update(database); + } + } + }); + } catch (SQLException e) { + throw new DatabaseException("Could not update MySQL database", e); } + super.update(database); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/database/version/Version.java b/Common/Source/src/main/java/dev/brighten/antivpn/database/version/Version.java index 2c714b4..76c1741 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/database/version/Version.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/database/version/Version.java @@ -28,13 +28,15 @@ import dev.brighten.antivpn.database.mongo.version.MongoThird; import dev.brighten.antivpn.database.sql.MySqlVPN; import dev.brighten.antivpn.database.sql.version.MySQLFirst; - public interface Version { - void update(DB database) throws DatabaseException; - int versionNumber(); - boolean needsUpdate(DB database); + void update(DB database) throws DatabaseException; - Version[] mongoDbVersions = new Version[] {new MongoFirst(), new MongoSecond(), new MongoThird()}; - Version[] mysqlVersions = new Version[] {new MySQLFirst(), new Second(), new Third()}; - Version[] h2Versions = new Version[] {new First(), new Second(), new Third()}; -} \ No newline at end of file + int versionNumber(); + + boolean needsUpdate(DB database); + + Version[] mongoDbVersions = + new Version[] {new MongoFirst(), new MongoSecond(), new MongoThird()}; + Version[] mysqlVersions = new Version[] {new MySQLFirst(), new Second(), new Third()}; + Version[] h2Versions = new Version[] {new First(), new Second(), new Third()}; +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java b/Common/Source/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java index 7a76d45..c6a228b 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/depends/LibraryLoader.java @@ -20,6 +20,16 @@ import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.utils.NonnullByDefault; import dev.brighten.antivpn.utils.Supplier; import dev.brighten.antivpn.utils.Suppliers; +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; import lombok.Getter; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; @@ -32,833 +42,911 @@ import org.objectweb.asm.RecordComponentVisitor; import org.objectweb.asm.commons.ClassRemapper; import org.objectweb.asm.commons.Remapper; -import java.io.*; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.util.*; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.JarOutputStream; - /** - * Resolves {@link MavenLibrary} annotations for a class, and loads the dependency - * into the classloader. + * Resolves {@link MavenLibrary} annotations for a class, and loads the dependency into the + * classloader. */ @SuppressWarnings("CallToPrintStackTrace") @NonnullByDefault public final class LibraryLoader { - private static final int RELOCATION_FORMAT_VERSION = 5; - private static final String RELOCATION_METADATA_PATH = "META-INF/antivpn-relocation.properties"; + private static final int RELOCATION_FORMAT_VERSION = 5; + private static final String RELOCATION_METADATA_PATH = "META-INF/antivpn-relocation.properties"; - @SuppressWarnings("Guava") - private static final Supplier URL_INJECTOR = AntiVPN.getInstance().getClass().getClassLoader() instanceof URLClassLoader ? - Suppliers.memoize(() -> - URLClassLoaderAccess.create((URLClassLoader) AntiVPN.getInstance().getClass().getClassLoader())) - : null; + @SuppressWarnings("Guava") + private static final Supplier URL_INJECTOR = + AntiVPN.getInstance().getClass().getClassLoader() instanceof URLClassLoader + ? Suppliers.memoize( + () -> + URLClassLoaderAccess.create( + (URLClassLoader) AntiVPN.getInstance().getClass().getClassLoader())) + : null; - public static void loadAll(Object object) { - if(URL_INJECTOR == null) - return; - loadAll(object.getClass()); + public static void loadAll(Object object) { + if (URL_INJECTOR == null) return; + loadAll(object.getClass()); + } + + public static void loadAll(Class clazz) { + if (URL_INJECTOR == null) return; + MavenLibrary[] libs = clazz.getDeclaredAnnotationsByType(MavenLibrary.class); + + for (MavenLibrary lib : libs) { + // Create relocations map if any are defined + Map relocations = new HashMap<>(); + for (Relocate relocate : lib.relocations()) { + relocations.put(relocate.from().replace("\\", ""), relocate.to()); + } + + load( + lib.groupId().replace("\\", ""), + lib.artifactId(), + lib.version(), + lib.repo().url(), + relocations); + } + } + + public static void load( + String groupId, + String artifactId, + String version, + String repoUrl, + Map relocations) { + load(new Dependency(groupId, artifactId, version, repoUrl), relocations); + } + + public static void load(Dependency d, Map relocations) { + System.out.printf( + "Loading dependency %s:%s:%s from %s%n", + d.getGroupId(), d.getArtifactId(), d.getVersion(), d.getRepoUrl()); + String name = d.getArtifactId() + "-" + d.getVersion(); + + // If we have relocations, add a suffix to identify the relocated version + String fileName = name + ".jar"; + if (!relocations.isEmpty()) { + fileName = name + "-relocated.jar"; } - public static void loadAll(Class clazz) { - if(URL_INJECTOR == null) - return; - MavenLibrary[] libs = clazz.getDeclaredAnnotationsByType(MavenLibrary.class); + File saveLocation = new File(getLibFolder(), fileName); + File originalJar = new File(getLibFolder(), name + ".jar"); - for (MavenLibrary lib : libs) { - // Create relocations map if any are defined - Map relocations = new HashMap<>(); - for (Relocate relocate : lib.relocations()) { - relocations.put(relocate.from().replace("\\", ""), relocate.to()); - } + // Download the original jar if it doesn't exist + if (!originalJar.exists()) { + try { + System.out.println( + "Dependency '" + + name + + "' is not already in the libraries folder. Attempting to download..."); + URL url = d.getUrl(); - load(lib.groupId().replace("\\", ""), lib.artifactId(), lib.version(), lib.repo().url(), relocations); + try (InputStream is = url.openStream()) { + Files.copy(is, originalJar.toPath()); } + System.out.println("Dependency '" + name + "' successfully downloaded."); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Unable to download dependency: " + d, e); + } } - public static void load(String groupId, String artifactId, String version, String repoUrl, - Map relocations) { - load(new Dependency(groupId, artifactId, version, repoUrl), relocations); + // Rebuild relocated jars when the relocation format changes or the cached jar is stale. + if (!relocations.isEmpty() && shouldRebuildRelocatedJar(saveLocation, relocations)) { + try { + System.out.println("Relocating packages for " + name + "..."); + relocateJar(originalJar, saveLocation, relocations); + System.out.println("Successfully relocated packages for " + name); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("Failed to relocate packages for dependency: " + d, e); + } } - public static void load(Dependency d, Map relocations) { - System.out.printf("Loading dependency %s:%s:%s from %s%n", - d.getGroupId(), d.getArtifactId(), d.getVersion(), d.getRepoUrl()); - String name = d.getArtifactId() + "-" + d.getVersion(); + // Load the appropriate jar (original or relocated) + File jarToLoad = relocations.isEmpty() ? originalJar : saveLocation; - // If we have relocations, add a suffix to identify the relocated version - String fileName = name + ".jar"; - if (!relocations.isEmpty()) { - fileName = name + "-relocated.jar"; + if (!jarToLoad.exists()) { + throw new RuntimeException("Unable to find dependency jar: " + jarToLoad.getAbsolutePath()); + } + + try { + URL_INJECTOR.get().addURL(jarToLoad.toURI().toURL()); + } catch (Exception e) { + throw new RuntimeException("Unable to load dependency: " + jarToLoad, e); + } + + System.out.println("Loaded dependency '" + name + "' successfully."); + } + + private static void relocateJar(File sourceJar, File targetJar, Map relocations) + throws IOException { + // Track service files to avoid duplicates + Map serviceFiles = new HashMap<>(); + + Files.deleteIfExists(targetJar.toPath()); + + try (JarFile jar = new JarFile(sourceJar); + JarOutputStream jos = new JarOutputStream(Files.newOutputStream(targetJar.toPath()))) { + + Enumeration entries = jar.entries(); + + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + + // Skip directories + if (entry.isDirectory()) { + continue; } - File saveLocation = new File(getLibFolder(), fileName); - File originalJar = new File(getLibFolder(), name + ".jar"); + try (InputStream is = jar.getInputStream(entry)) { + if (name.startsWith("META-INF/services/")) { + // Process service files but don't write yet + processServiceFile(name, is, serviceFiles, relocations); + } else if (name.endsWith(".class")) { + // Relocate class file path as well as content + String relocatedPath = relocateClassPath(name, relocations); - // Download the original jar if it doesn't exist - if (!originalJar.exists()) { - try { - System.out.println("Dependency '" + name + - "' is not already in the libraries folder. Attempting to download..."); - URL url = d.getUrl(); + JarEntry newEntry = new JarEntry(relocatedPath); + jos.putNextEntry(newEntry); - try (InputStream is = url.openStream()) { - Files.copy(is, originalJar.toPath()); - } - System.out.println("Dependency '" + name + "' successfully downloaded."); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException("Unable to download dependency: " + d, e); - } - } - - // Rebuild relocated jars when the relocation format changes or the cached jar is stale. - if (!relocations.isEmpty() && shouldRebuildRelocatedJar(saveLocation, relocations)) { - try { - System.out.println("Relocating packages for " + name + "..."); - relocateJar(originalJar, saveLocation, relocations); - System.out.println("Successfully relocated packages for " + name); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException("Failed to relocate packages for dependency: " + d, e); - } - } - - // Load the appropriate jar (original or relocated) - File jarToLoad = relocations.isEmpty() ? originalJar : saveLocation; - - if (!jarToLoad.exists()) { - throw new RuntimeException("Unable to find dependency jar: " + jarToLoad.getAbsolutePath()); + byte[] classBytes = readAllBytes(is); + byte[] relocatedBytes = relocateClass(name, classBytes, relocations); + jos.write(relocatedBytes); + jos.closeEntry(); + } else { + // Relocate package-scoped resources so ResourceBundle lookups follow relocated + // packages. + String relocatedPath = relocateResourcePath(name, relocations); + + JarEntry newEntry = new JarEntry(relocatedPath); + jos.putNextEntry(newEntry); + copyStream(is, jos); + jos.closeEntry(); + } } + } + // Now write all service files after processing + for (Map.Entry entry : serviceFiles.entrySet()) { try { - URL_INJECTOR.get().addURL(jarToLoad.toURI().toURL()); + JarEntry serviceEntry = new JarEntry(entry.getKey()); + jos.putNextEntry(serviceEntry); + jos.write(entry.getValue().toString().getBytes(StandardCharsets.UTF_8)); + jos.closeEntry(); } catch (Exception e) { - throw new RuntimeException("Unable to load dependency: " + jarToLoad, e); + // Log but continue with other service files + System.out.println( + "Warning: Could not write service file " + entry.getKey() + ": " + e.getMessage()); } + } - System.out.println("Loaded dependency '" + name + "' successfully."); + writeRelocationMetadata(jos, relocations); } - private static void relocateJar(File sourceJar, File targetJar, Map relocations) - throws IOException { - // Track service files to avoid duplicates - Map serviceFiles = new HashMap<>(); + validateRelocatedJar(targetJar, relocations); + } - Files.deleteIfExists(targetJar.toPath()); - - try (JarFile jar = new JarFile(sourceJar); - JarOutputStream jos = new JarOutputStream(Files.newOutputStream(targetJar.toPath()))) { - - Enumeration entries = jar.entries(); - - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - String name = entry.getName(); - - // Skip directories - if (entry.isDirectory()) { - continue; - } - - try (InputStream is = jar.getInputStream(entry)) { - if (name.startsWith("META-INF/services/")) { - // Process service files but don't write yet - processServiceFile(name, is, serviceFiles, relocations); - } else if (name.endsWith(".class")) { - // Relocate class file path as well as content - String relocatedPath = relocateClassPath(name, relocations); - - JarEntry newEntry = new JarEntry(relocatedPath); - jos.putNextEntry(newEntry); - - byte[] classBytes = readAllBytes(is); - byte[] relocatedBytes = relocateClass(name, classBytes, relocations); - jos.write(relocatedBytes); - jos.closeEntry(); - } else { - // Relocate package-scoped resources so ResourceBundle lookups follow relocated packages. - String relocatedPath = relocateResourcePath(name, relocations); - - JarEntry newEntry = new JarEntry(relocatedPath); - jos.putNextEntry(newEntry); - copyStream(is, jos); - jos.closeEntry(); - } - } - } - - // Now write all service files after processing - for (Map.Entry entry : serviceFiles.entrySet()) { - try { - JarEntry serviceEntry = new JarEntry(entry.getKey()); - jos.putNextEntry(serviceEntry); - jos.write(entry.getValue().toString().getBytes(StandardCharsets.UTF_8)); - jos.closeEntry(); - } catch (Exception e) { - // Log but continue with other service files - System.out.println("Warning: Could not write service file " + - entry.getKey() + ": " + e.getMessage()); - } - } - - writeRelocationMetadata(jos, relocations); - } - - validateRelocatedJar(targetJar, relocations); + private static boolean shouldRebuildRelocatedJar( + File relocatedJar, Map relocations) { + if (!relocatedJar.exists()) { + return true; } - private static boolean shouldRebuildRelocatedJar(File relocatedJar, Map relocations) { - if (!relocatedJar.exists()) { - return true; + try (JarFile jar = new JarFile(relocatedJar)) { + JarEntry metadataEntry = jar.getJarEntry(RELOCATION_METADATA_PATH); + if (metadataEntry == null) { + return true; + } + + Properties metadata = new Properties(); + try (InputStream is = jar.getInputStream(metadataEntry)) { + metadata.load(is); + } + + if (!String.valueOf(RELOCATION_FORMAT_VERSION) + .equals(metadata.getProperty("formatVersion"))) { + return true; + } + + for (Map.Entry relocation : relocations.entrySet()) { + String key = "relocation." + relocation.getKey(); + if (!relocation.getValue().equals(metadata.getProperty(key))) { + return true; } + } - try (JarFile jar = new JarFile(relocatedJar)) { - JarEntry metadataEntry = jar.getJarEntry(RELOCATION_METADATA_PATH); - if (metadataEntry == null) { - return true; - } + return Integer.toString(relocations.size()).equals(metadata.getProperty("relocationCount")); + } catch (IOException e) { + return true; + } + } - Properties metadata = new Properties(); - try (InputStream is = jar.getInputStream(metadataEntry)) { - metadata.load(is); - } + private static void writeRelocationMetadata(JarOutputStream jos, Map relocations) + throws IOException { + Properties metadata = new Properties(); + metadata.setProperty("formatVersion", Integer.toString(RELOCATION_FORMAT_VERSION)); + metadata.setProperty("relocationCount", Integer.toString(relocations.size())); - if (!String.valueOf(RELOCATION_FORMAT_VERSION).equals(metadata.getProperty("formatVersion"))) { - return true; - } - - for (Map.Entry relocation : relocations.entrySet()) { - String key = "relocation." + relocation.getKey(); - if (!relocation.getValue().equals(metadata.getProperty(key))) { - return true; - } - } - - return Integer.toString(relocations.size()).equals(metadata.getProperty("relocationCount")); - } catch (IOException e) { - return true; - } + Map sortedRelocations = new TreeMap<>(relocations); + for (Map.Entry relocation : sortedRelocations.entrySet()) { + metadata.setProperty("relocation." + relocation.getKey(), relocation.getValue()); } - private static void writeRelocationMetadata(JarOutputStream jos, Map relocations) - throws IOException { - Properties metadata = new Properties(); - metadata.setProperty("formatVersion", Integer.toString(RELOCATION_FORMAT_VERSION)); - metadata.setProperty("relocationCount", Integer.toString(relocations.size())); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + metadata.store(buffer, "AntiVPN relocation metadata"); - Map sortedRelocations = new TreeMap<>(relocations); - for (Map.Entry relocation : sortedRelocations.entrySet()) { - metadata.setProperty("relocation." + relocation.getKey(), relocation.getValue()); + JarEntry metadataEntry = new JarEntry(RELOCATION_METADATA_PATH); + jos.putNextEntry(metadataEntry); + jos.write(buffer.toByteArray()); + jos.closeEntry(); + } + + private static void processServiceFile( + String name, + InputStream is, + Map serviceFiles, + Map relocations) + throws IOException { + // Read service file content + String content = new String(readAllBytes(is)); + StringBuilder contentBuilder = serviceFiles.computeIfAbsent(name, k -> new StringBuilder()); + + // Process and relocate service implementations + for (String line : content.split("\n")) { + String trimmed = line.trim(); + if (!trimmed.isEmpty() && !trimmed.startsWith("#")) { + for (Map.Entry relocation : relocations.entrySet()) { + if (trimmed.startsWith(relocation.getKey())) { + trimmed = relocation.getValue() + trimmed.substring(relocation.getKey().length()); + break; + } } - - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - metadata.store(buffer, "AntiVPN relocation metadata"); - - JarEntry metadataEntry = new JarEntry(RELOCATION_METADATA_PATH); - jos.putNextEntry(metadataEntry); - jos.write(buffer.toByteArray()); - jos.closeEntry(); + } + contentBuilder.append(trimmed).append("\n"); } + } - private static void processServiceFile(String name, InputStream is, - Map serviceFiles, - Map relocations) throws IOException { - // Read service file content - String content = new String(readAllBytes(is)); - StringBuilder contentBuilder = serviceFiles.computeIfAbsent(name, k -> new StringBuilder()); + private static byte[] relocateClass( + String entryName, byte[] classBytes, Map relocations) { + try { + // Convert to slash notation for ASM + Remapper prefixRemapper = getPrefixRemapper(relocations); - // Process and relocate service implementations - for (String line : content.split("\n")) { - String trimmed = line.trim(); - if (!trimmed.isEmpty() && !trimmed.startsWith("#")) { - for (Map.Entry relocation : relocations.entrySet()) { - if (trimmed.startsWith(relocation.getKey())) { - trimmed = relocation.getValue() + - trimmed.substring(relocation.getKey().length()); - break; - } - } - } - contentBuilder.append(trimmed).append("\n"); - } - } - - private static byte[] relocateClass(String entryName, byte[] classBytes, Map relocations) { - try { - // Convert to slash notation for ASM - Remapper prefixRemapper = getPrefixRemapper(relocations); - - // Create custom ClassWriter to handle missing classes - ClassReader reader = new ClassReader(classBytes); - ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS) { - @Override - protected String getCommonSuperClass(String type1, String type2) { - try { - return super.getCommonSuperClass(type1, type2); - } catch (RuntimeException e) { - // Fall back to Object when classes can't be loaded - return "java/lang/Object"; - } - } - }; - - ClassVisitor visitor = createStringRelocationVisitor(new ClassRemapper(writer, prefixRemapper), relocations); - visitor = createMySqlUtilFallbackVisitor(entryName, visitor); - - // Process class with remapper - reader.accept(visitor, 0); - - return relocateUtf8Constants(writer.toByteArray(), relocations); - } catch (Exception e) { - throw new IllegalStateException("Failed to relocate class entry " + entryName, e); - } - } - - public static String relocateReflectiveClassName(String className) { - if (className == null || className.startsWith("dev.brighten.antivpn.shaded.")) { - return className; - } - - if (className.startsWith("com.mysql.cj") || className.startsWith("com.mysql.jdbc")) { - return "dev.brighten.antivpn.shaded." + className; - } - - return className; - } - - private static byte[] relocateUtf8Constants(byte[] classBytes, Map relocations) throws IOException { - Map dotMappings = new HashMap<>(); - Map slashMappings = new HashMap<>(); - for (Map.Entry entry : relocations.entrySet()) { - dotMappings.put(entry.getKey(), entry.getValue()); - slashMappings.put(entry.getKey().replace('.', '/'), entry.getValue().replace('.', '/')); - } - - DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes)); - ByteArrayOutputStream baos = new ByteArrayOutputStream(classBytes.length + 256); - DataOutputStream out = new DataOutputStream(baos); - - out.writeInt(in.readInt()); - out.writeShort(in.readUnsignedShort()); - out.writeShort(in.readUnsignedShort()); - - int constantPoolCount = in.readUnsignedShort(); - out.writeShort(constantPoolCount); - - for (int i = 1; i < constantPoolCount; i++) { - int tag = in.readUnsignedByte(); - out.writeByte(tag); - - switch (tag) { - case 1 -> { - String value = in.readUTF(); - String relocated = relocateStringValue(value, dotMappings, slashMappings); - out.writeUTF(relocated); - } - case 3, 4 -> out.writeInt(in.readInt()); - case 5, 6 -> { - out.writeLong(in.readLong()); - i++; - } - case 7, 8, 16, 19, 20 -> out.writeShort(in.readUnsignedShort()); - case 9, 10, 11, 12, 17, 18 -> { - out.writeShort(in.readUnsignedShort()); - out.writeShort(in.readUnsignedShort()); - } - case 15 -> { - out.writeByte(in.readUnsignedByte()); - out.writeShort(in.readUnsignedShort()); - } - default -> throw new IOException("Unknown constant pool tag " + tag); - } - } - - copyStream(in, out); - out.flush(); - return baos.toByteArray(); - } - - private static Remapper getPrefixRemapper(Map relocations) { - Map slashMappings = new HashMap<>(); - Map dotMappings = new HashMap<>(); - for (Map.Entry entry : relocations.entrySet()) { - dotMappings.put(entry.getKey(), entry.getValue()); - String fromSlash = entry.getKey().replace('.', '/'); - String toSlash = entry.getValue().replace('.', '/'); - slashMappings.put(fromSlash, toSlash); - } - - // Create customized remapper for package prefixes - return new Remapper() { + // Create custom ClassWriter to handle missing classes + ClassReader reader = new ClassReader(classBytes); + ClassWriter writer = + new ClassWriter(ClassWriter.COMPUTE_MAXS) { @Override - public String map(String typeName) { - if (typeName == null) return null; - - for (Map.Entry entry : slashMappings.entrySet()) { - String from = entry.getKey(); - String to = entry.getValue(); - - if (typeName.startsWith(from)) { - return to + typeName.substring(from.length()); - } - } - return typeName; + protected String getCommonSuperClass(String type1, String type2) { + try { + return super.getCommonSuperClass(type1, type2); + } catch (RuntimeException e) { + // Fall back to Object when classes can't be loaded + return "java/lang/Object"; + } } + }; - @Override - public Object mapValue(Object value) { - if (value instanceof String stringValue) { - return relocateStringValue(stringValue, dotMappings, slashMappings); - } - return super.mapValue(value); - } - }; + ClassVisitor visitor = + createStringRelocationVisitor(new ClassRemapper(writer, prefixRemapper), relocations); + visitor = createMySqlUtilFallbackVisitor(entryName, visitor); + + // Process class with remapper + reader.accept(visitor, 0); + + return relocateUtf8Constants(writer.toByteArray(), relocations); + } catch (Exception e) { + throw new IllegalStateException("Failed to relocate class entry " + entryName, e); + } + } + + public static String relocateReflectiveClassName(String className) { + if (className == null || className.startsWith("dev.brighten.antivpn.shaded.")) { + return className; } - private static ClassVisitor createMySqlUtilFallbackVisitor(String entryName, ClassVisitor delegate) { - if (!"com/mysql/cj/util/Util.class".equals(entryName)) { - return delegate; - } - - return new ClassVisitor(Opcodes.ASM9, delegate) { - @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, - String[] exceptions) { - MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions); - if (visitor == null) { - return null; - } - - if (!"getInstance".equals(name) - || !"(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;[Ljava/lang/Object;Lcom/mysql/cj/exceptions/ExceptionInterceptor;)Ljava/lang/Object;".equals(descriptor)) { - return visitor; - } - - return new MethodVisitor(Opcodes.ASM9, visitor) { - @Override - public void visitCode() { - super.visitCode(); - super.visitVarInsn(Opcodes.ALOAD, 1); - super.visitMethodInsn(Opcodes.INVOKESTATIC, - "dev/brighten/antivpn/depends/LibraryLoader", - "relocateReflectiveClassName", - "(Ljava/lang/String;)Ljava/lang/String;", - false); - super.visitVarInsn(Opcodes.ASTORE, 1); - } - }; - } - }; + if (className.startsWith("com.mysql.cj") || className.startsWith("com.mysql.jdbc")) { + return "dev.brighten.antivpn.shaded." + className; } - private static ClassVisitor createStringRelocationVisitor(ClassVisitor delegate, - Map relocations) { - Map dotMappings = new HashMap<>(); - Map slashMappings = new HashMap<>(); - for (Map.Entry entry : relocations.entrySet()) { - dotMappings.put(entry.getKey(), entry.getValue()); - slashMappings.put(entry.getKey().replace('.', '/'), entry.getValue().replace('.', '/')); - } + return className; + } - return new ClassVisitor(Opcodes.ASM9, delegate) { - @Override - public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { - return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible), dotMappings, slashMappings); - } - - @Override - public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath, - String descriptor, boolean visible) { - return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible), - dotMappings, slashMappings); - } - - @Override - public RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) { - RecordComponentVisitor visitor = super.visitRecordComponent(name, descriptor, signature); - if (visitor == null) { - return null; - } - return new RecordComponentVisitor(Opcodes.ASM9, visitor) { - @Override - public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { - return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible), - dotMappings, slashMappings); - } - - @Override - public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath, - String descriptor, boolean visible) { - return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible), - dotMappings, slashMappings); - } - }; - } - - @Override - public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { - FieldVisitor visitor = super.visitField(access, name, descriptor, signature, - relocateAsmValue(value, dotMappings, slashMappings)); - if (visitor == null) { - return null; - } - return new FieldVisitor(Opcodes.ASM9, visitor) { - @Override - public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { - return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible), - dotMappings, slashMappings); - } - - @Override - public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath, - String descriptor, boolean visible) { - return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible), - dotMappings, slashMappings); - } - }; - } - - @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, - String[] exceptions) { - MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions); - if (visitor == null) { - return null; - } - return new MethodVisitor(Opcodes.ASM9, visitor) { - @Override - public AnnotationVisitor visitAnnotationDefault() { - return wrapAnnotationVisitor(super.visitAnnotationDefault(), dotMappings, slashMappings); - } - - @Override - public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { - return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible), - dotMappings, slashMappings); - } - - @Override - public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath, - String descriptor, boolean visible) { - return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible), - dotMappings, slashMappings); - } - - @Override - public AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, - boolean visible) { - return wrapAnnotationVisitor(super.visitParameterAnnotation(parameter, descriptor, visible), - dotMappings, slashMappings); - } - - @Override - public AnnotationVisitor visitInsnAnnotation(int typeRef, org.objectweb.asm.TypePath typePath, - String descriptor, boolean visible) { - return wrapAnnotationVisitor(super.visitInsnAnnotation(typeRef, typePath, descriptor, visible), - dotMappings, slashMappings); - } - - @Override - public AnnotationVisitor visitTryCatchAnnotation(int typeRef, org.objectweb.asm.TypePath typePath, - String descriptor, boolean visible) { - return wrapAnnotationVisitor(super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible), - dotMappings, slashMappings); - } - - @Override - public AnnotationVisitor visitLocalVariableAnnotation(int typeRef, - org.objectweb.asm.TypePath typePath, - org.objectweb.asm.Label[] start, - org.objectweb.asm.Label[] end, - int[] index, String descriptor, - boolean visible) { - return wrapAnnotationVisitor( - super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible), - dotMappings, slashMappings); - } - - @Override - public void visitLdcInsn(Object value) { - super.visitLdcInsn(relocateAsmValue(value, dotMappings, slashMappings)); - } - - @Override - public void visitInvokeDynamicInsn(String name, String descriptor, org.objectweb.asm.Handle bootstrapMethodHandle, - Object... bootstrapMethodArguments) { - Object[] relocatedArgs = new Object[bootstrapMethodArguments.length]; - for (int i = 0; i < bootstrapMethodArguments.length; i++) { - relocatedArgs[i] = relocateAsmValue(bootstrapMethodArguments[i], dotMappings, slashMappings); - } - super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, relocatedArgs); - } - }; - } - }; + private static byte[] relocateUtf8Constants(byte[] classBytes, Map relocations) + throws IOException { + Map dotMappings = new HashMap<>(); + Map slashMappings = new HashMap<>(); + for (Map.Entry entry : relocations.entrySet()) { + dotMappings.put(entry.getKey(), entry.getValue()); + slashMappings.put(entry.getKey().replace('.', '/'), entry.getValue().replace('.', '/')); } - private static AnnotationVisitor wrapAnnotationVisitor(AnnotationVisitor delegate, - Map dotMappings, - Map slashMappings) { - if (delegate == null) { - return null; + DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(classBytes.length + 256); + DataOutputStream out = new DataOutputStream(baos); + + out.writeInt(in.readInt()); + out.writeShort(in.readUnsignedShort()); + out.writeShort(in.readUnsignedShort()); + + int constantPoolCount = in.readUnsignedShort(); + out.writeShort(constantPoolCount); + + for (int i = 1; i < constantPoolCount; i++) { + int tag = in.readUnsignedByte(); + out.writeByte(tag); + + switch (tag) { + case 1 -> { + String value = in.readUTF(); + String relocated = relocateStringValue(value, dotMappings, slashMappings); + out.writeUTF(relocated); } - - return new AnnotationVisitor(Opcodes.ASM9, delegate) { - @Override - public void visit(String name, Object value) { - super.visit(name, relocateAsmValue(value, dotMappings, slashMappings)); - } - - @Override - public AnnotationVisitor visitAnnotation(String name, String descriptor) { - return wrapAnnotationVisitor(super.visitAnnotation(name, descriptor), dotMappings, slashMappings); - } - - @Override - public AnnotationVisitor visitArray(String name) { - return wrapAnnotationVisitor(super.visitArray(name), dotMappings, slashMappings); - } - }; + case 3, 4 -> out.writeInt(in.readInt()); + case 5, 6 -> { + out.writeLong(in.readLong()); + i++; + } + case 7, 8, 16, 19, 20 -> out.writeShort(in.readUnsignedShort()); + case 9, 10, 11, 12, 17, 18 -> { + out.writeShort(in.readUnsignedShort()); + out.writeShort(in.readUnsignedShort()); + } + case 15 -> { + out.writeByte(in.readUnsignedByte()); + out.writeShort(in.readUnsignedShort()); + } + default -> throw new IOException("Unknown constant pool tag " + tag); + } } - private static Object relocateAsmValue(Object value, Map dotMappings, - Map slashMappings) { - if (value instanceof String stringValue) { - return relocateStringValue(stringValue, dotMappings, slashMappings); - } + copyStream(in, out); + out.flush(); + return baos.toByteArray(); + } - return value; + private static Remapper getPrefixRemapper(Map relocations) { + Map slashMappings = new HashMap<>(); + Map dotMappings = new HashMap<>(); + for (Map.Entry entry : relocations.entrySet()) { + dotMappings.put(entry.getKey(), entry.getValue()); + String fromSlash = entry.getKey().replace('.', '/'); + String toSlash = entry.getValue().replace('.', '/'); + slashMappings.put(fromSlash, toSlash); } - private static String relocateStringValue(String value, Map dotMappings, - Map slashMappings) { - for (Map.Entry entry : dotMappings.entrySet()) { - String from = entry.getKey(); - String relocated = relocateByPrefixes(value, from, entry.getValue(), '.', '$'); - if (!relocated.equals(value)) { - return relocated; - } - } + // Create customized remapper for package prefixes + return new Remapper() { + @Override + public String map(String typeName) { + if (typeName == null) return null; for (Map.Entry entry : slashMappings.entrySet()) { - String from = entry.getKey(); - String to = entry.getValue(); + String from = entry.getKey(); + String to = entry.getValue(); - String relocated = relocateByPrefixes(value, from, to, '/', '$'); - if (!relocated.equals(value)) { - return relocated; - } - - relocated = relocateByPrefixes(value, "/" + from, "/" + to, '/', '$'); - if (!relocated.equals(value)) { - return relocated; - } - - relocated = relocateByPrefixes(value, "L" + from, "L" + to, '/', '$', ';'); - if (!relocated.equals(value)) { - return relocated; - } - - relocated = relocateByPrefixes(value, "[L" + from, "[L" + to, '/', '$', ';'); - if (!relocated.equals(value)) { - return relocated; - } + if (typeName.startsWith(from)) { + return to + typeName.substring(from.length()); + } } + return typeName; + } - return value; + @Override + public Object mapValue(Object value) { + if (value instanceof String stringValue) { + return relocateStringValue(stringValue, dotMappings, slashMappings); + } + return super.mapValue(value); + } + }; + } + + private static ClassVisitor createMySqlUtilFallbackVisitor( + String entryName, ClassVisitor delegate) { + if (!"com/mysql/cj/util/Util.class".equals(entryName)) { + return delegate; } - private static String relocateByPrefixes(String value, String from, String to, char... delimiters) { - if (value.equals(from)) { - return to; + return new ClassVisitor(Opcodes.ASM9, delegate) { + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions); + if (visitor == null) { + return null; } - for (char delimiter : delimiters) { - if (value.startsWith(from + delimiter)) { - return to + value.substring(from.length()); + if (!"getInstance".equals(name) + || !"(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;[Ljava/lang/Object;Lcom/mysql/cj/exceptions/ExceptionInterceptor;)Ljava/lang/Object;" + .equals(descriptor)) { + return visitor; + } + + return new MethodVisitor(Opcodes.ASM9, visitor) { + @Override + public void visitCode() { + super.visitCode(); + super.visitVarInsn(Opcodes.ALOAD, 1); + super.visitMethodInsn( + Opcodes.INVOKESTATIC, + "dev/brighten/antivpn/depends/LibraryLoader", + "relocateReflectiveClassName", + "(Ljava/lang/String;)Ljava/lang/String;", + false); + super.visitVarInsn(Opcodes.ASTORE, 1); + } + }; + } + }; + } + + private static ClassVisitor createStringRelocationVisitor( + ClassVisitor delegate, Map relocations) { + Map dotMappings = new HashMap<>(); + Map slashMappings = new HashMap<>(); + for (Map.Entry entry : relocations.entrySet()) { + dotMappings.put(entry.getKey(), entry.getValue()); + slashMappings.put(entry.getKey().replace('.', '/'), entry.getValue().replace('.', '/')); + } + + return new ClassVisitor(Opcodes.ASM9, delegate) { + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return wrapAnnotationVisitor( + super.visitAnnotation(descriptor, visible), dotMappings, slashMappings); + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + int typeRef, org.objectweb.asm.TypePath typePath, String descriptor, boolean visible) { + return wrapAnnotationVisitor( + super.visitTypeAnnotation(typeRef, typePath, descriptor, visible), + dotMappings, + slashMappings); + } + + @Override + public RecordComponentVisitor visitRecordComponent( + String name, String descriptor, String signature) { + RecordComponentVisitor visitor = super.visitRecordComponent(name, descriptor, signature); + if (visitor == null) { + return null; + } + return new RecordComponentVisitor(Opcodes.ASM9, visitor) { + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return wrapAnnotationVisitor( + super.visitAnnotation(descriptor, visible), dotMappings, slashMappings); + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + int typeRef, + org.objectweb.asm.TypePath typePath, + String descriptor, + boolean visible) { + return wrapAnnotationVisitor( + super.visitTypeAnnotation(typeRef, typePath, descriptor, visible), + dotMappings, + slashMappings); + } + }; + } + + @Override + public FieldVisitor visitField( + int access, String name, String descriptor, String signature, Object value) { + FieldVisitor visitor = + super.visitField( + access, + name, + descriptor, + signature, + relocateAsmValue(value, dotMappings, slashMappings)); + if (visitor == null) { + return null; + } + return new FieldVisitor(Opcodes.ASM9, visitor) { + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return wrapAnnotationVisitor( + super.visitAnnotation(descriptor, visible), dotMappings, slashMappings); + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + int typeRef, + org.objectweb.asm.TypePath typePath, + String descriptor, + boolean visible) { + return wrapAnnotationVisitor( + super.visitTypeAnnotation(typeRef, typePath, descriptor, visible), + dotMappings, + slashMappings); + } + }; + } + + @Override + public MethodVisitor visitMethod( + int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions); + if (visitor == null) { + return null; + } + return new MethodVisitor(Opcodes.ASM9, visitor) { + @Override + public AnnotationVisitor visitAnnotationDefault() { + return wrapAnnotationVisitor( + super.visitAnnotationDefault(), dotMappings, slashMappings); + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + return wrapAnnotationVisitor( + super.visitAnnotation(descriptor, visible), dotMappings, slashMappings); + } + + @Override + public AnnotationVisitor visitTypeAnnotation( + int typeRef, + org.objectweb.asm.TypePath typePath, + String descriptor, + boolean visible) { + return wrapAnnotationVisitor( + super.visitTypeAnnotation(typeRef, typePath, descriptor, visible), + dotMappings, + slashMappings); + } + + @Override + public AnnotationVisitor visitParameterAnnotation( + int parameter, String descriptor, boolean visible) { + return wrapAnnotationVisitor( + super.visitParameterAnnotation(parameter, descriptor, visible), + dotMappings, + slashMappings); + } + + @Override + public AnnotationVisitor visitInsnAnnotation( + int typeRef, + org.objectweb.asm.TypePath typePath, + String descriptor, + boolean visible) { + return wrapAnnotationVisitor( + super.visitInsnAnnotation(typeRef, typePath, descriptor, visible), + dotMappings, + slashMappings); + } + + @Override + public AnnotationVisitor visitTryCatchAnnotation( + int typeRef, + org.objectweb.asm.TypePath typePath, + String descriptor, + boolean visible) { + return wrapAnnotationVisitor( + super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible), + dotMappings, + slashMappings); + } + + @Override + public AnnotationVisitor visitLocalVariableAnnotation( + int typeRef, + org.objectweb.asm.TypePath typePath, + org.objectweb.asm.Label[] start, + org.objectweb.asm.Label[] end, + int[] index, + String descriptor, + boolean visible) { + return wrapAnnotationVisitor( + super.visitLocalVariableAnnotation( + typeRef, typePath, start, end, index, descriptor, visible), + dotMappings, + slashMappings); + } + + @Override + public void visitLdcInsn(Object value) { + super.visitLdcInsn(relocateAsmValue(value, dotMappings, slashMappings)); + } + + @Override + public void visitInvokeDynamicInsn( + String name, + String descriptor, + org.objectweb.asm.Handle bootstrapMethodHandle, + Object... bootstrapMethodArguments) { + Object[] relocatedArgs = new Object[bootstrapMethodArguments.length]; + for (int i = 0; i < bootstrapMethodArguments.length; i++) { + relocatedArgs[i] = + relocateAsmValue(bootstrapMethodArguments[i], dotMappings, slashMappings); } - } + super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, relocatedArgs); + } + }; + } + }; + } - return value; + private static AnnotationVisitor wrapAnnotationVisitor( + AnnotationVisitor delegate, + Map dotMappings, + Map slashMappings) { + if (delegate == null) { + return null; } - private static void validateRelocatedJar(File targetJar, Map relocations) throws IOException { - Set relocatedPrefixes = new HashSet<>(); - Map dotMappings = new HashMap<>(); - Map slashMappings = new HashMap<>(); - for (Map.Entry relocation : relocations.entrySet()) { - relocatedPrefixes.add(relocation.getValue().replace('.', '/') + "/"); - dotMappings.put(relocation.getKey(), relocation.getValue()); - slashMappings.put(relocation.getKey().replace('.', '/'), relocation.getValue().replace('.', '/')); - } + return new AnnotationVisitor(Opcodes.ASM9, delegate) { + @Override + public void visit(String name, Object value) { + super.visit(name, relocateAsmValue(value, dotMappings, slashMappings)); + } - try (JarFile jar = new JarFile(targetJar)) { - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - if (entry.isDirectory() || !entry.getName().endsWith(".class")) { - continue; - } + @Override + public AnnotationVisitor visitAnnotation(String name, String descriptor) { + return wrapAnnotationVisitor( + super.visitAnnotation(name, descriptor), dotMappings, slashMappings); + } - boolean shouldValidate = false; - for (String relocatedPrefix : relocatedPrefixes) { - if (entry.getName().startsWith(relocatedPrefix)) { - shouldValidate = true; - break; - } - } + @Override + public AnnotationVisitor visitArray(String name) { + return wrapAnnotationVisitor(super.visitArray(name), dotMappings, slashMappings); + } + }; + } - if (!shouldValidate) { - continue; - } - - try (InputStream is = jar.getInputStream(entry)) { - findUnrelocatedConstant(entry.getName(), readAllBytes(is), dotMappings, slashMappings); - } - } - } + private static Object relocateAsmValue( + Object value, Map dotMappings, Map slashMappings) { + if (value instanceof String stringValue) { + return relocateStringValue(stringValue, dotMappings, slashMappings); } - private static void findUnrelocatedConstant(String entryName, byte[] classBytes, Map dotMappings, - Map slashMappings) throws IOException { - DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes)); - in.readInt(); - in.readUnsignedShort(); - in.readUnsignedShort(); - int constantPoolCount = in.readUnsignedShort(); + return value; + } - for (int i = 1; i < constantPoolCount; i++) { - int tag = in.readUnsignedByte(); - switch (tag) { - case 1 -> { - String value = in.readUTF(); - String relocated = relocateStringValue(value, dotMappings, slashMappings); - if (!value.equals(relocated)) { - throw new IOException("Relocated jar still contains original reference '" + value - + "' in class entry " + entryName); - } - } - case 3, 4 -> in.readInt(); - case 5, 6 -> { - in.readLong(); - i++; - } - case 7, 8, 16, 19, 20 -> in.readUnsignedShort(); - case 9, 10, 11, 12, 17, 18 -> { - in.readUnsignedShort(); - in.readUnsignedShort(); - } - case 15 -> { - in.readUnsignedByte(); - in.readUnsignedShort(); - } - default -> throw new IOException("Unknown constant pool tag " + tag + " while validating " + entryName); - } - } + private static String relocateStringValue( + String value, Map dotMappings, Map slashMappings) { + for (Map.Entry entry : dotMappings.entrySet()) { + String from = entry.getKey(); + String relocated = relocateByPrefixes(value, from, entry.getValue(), '.', '$'); + if (!relocated.equals(value)) { + return relocated; + } } - private static String relocateClassPath(String path, Map relocations) { - // Convert path to package format (replacing / with .) - String packagePath = path.substring(0, path.length() - 6).replace('/', '.'); + for (Map.Entry entry : slashMappings.entrySet()) { + String from = entry.getKey(); + String to = entry.getValue(); - // Apply relocations - for (Map.Entry relocation : relocations.entrySet()) { - if (packagePath.startsWith(relocation.getKey())) { - packagePath = relocation.getValue() + packagePath.substring(relocation.getKey().length()); - break; - } - } + String relocated = relocateByPrefixes(value, from, to, '/', '$'); + if (!relocated.equals(value)) { + return relocated; + } - // Convert back to path format - return packagePath.replace('.', '/') + ".class"; + relocated = relocateByPrefixes(value, "/" + from, "/" + to, '/', '$'); + if (!relocated.equals(value)) { + return relocated; + } + + relocated = relocateByPrefixes(value, "L" + from, "L" + to, '/', '$', ';'); + if (!relocated.equals(value)) { + return relocated; + } + + relocated = relocateByPrefixes(value, "[L" + from, "[L" + to, '/', '$', ';'); + if (!relocated.equals(value)) { + return relocated; + } } - private static String relocateResourcePath(String path, Map relocations) { - if (path.startsWith("META-INF/")) { - return path; - } + return value; + } - for (Map.Entry relocation : relocations.entrySet()) { - String fromPath = relocation.getKey().replace('.', '/'); - String toPath = relocation.getValue().replace('.', '/'); - - if (path.startsWith(fromPath + "/")) { - return toPath + path.substring(fromPath.length()); - } - } - - return path; + private static String relocateByPrefixes( + String value, String from, String to, char... delimiters) { + if (value.equals(from)) { + return to; } - private static byte[] readAllBytes(InputStream is) throws IOException { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - int bytesRead; - byte[] data = new byte[1024]; - while ((bytesRead = is.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, bytesRead); - } - return buffer.toByteArray(); + for (char delimiter : delimiters) { + if (value.startsWith(from + delimiter)) { + return to + value.substring(from.length()); + } } - private static void copyStream(InputStream is, OutputStream os) throws IOException { - byte[] buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = is.read(buffer)) != -1) { - os.write(buffer, 0, bytesRead); - } + return value; + } + + private static void validateRelocatedJar(File targetJar, Map relocations) + throws IOException { + Set relocatedPrefixes = new HashSet<>(); + Map dotMappings = new HashMap<>(); + Map slashMappings = new HashMap<>(); + for (Map.Entry relocation : relocations.entrySet()) { + relocatedPrefixes.add(relocation.getValue().replace('.', '/') + "/"); + dotMappings.put(relocation.getKey(), relocation.getValue()); + slashMappings.put( + relocation.getKey().replace('.', '/'), relocation.getValue().replace('.', '/')); } - private static File getLibFolder() { - File pluginDataFolder = AntiVPN.getInstance().getPluginFolder(); - File libs = new File(pluginDataFolder, "libraries"); - if(libs.mkdirs()) { - System.out.println("Created libraries folder!"); + try (JarFile jar = new JarFile(targetJar)) { + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (entry.isDirectory() || !entry.getName().endsWith(".class")) { + continue; } - return libs; + + boolean shouldValidate = false; + for (String relocatedPrefix : relocatedPrefixes) { + if (entry.getName().startsWith(relocatedPrefix)) { + shouldValidate = true; + break; + } + } + + if (!shouldValidate) { + continue; + } + + try (InputStream is = jar.getInputStream(entry)) { + findUnrelocatedConstant(entry.getName(), readAllBytes(is), dotMappings, slashMappings); + } + } + } + } + + private static void findUnrelocatedConstant( + String entryName, + byte[] classBytes, + Map dotMappings, + Map slashMappings) + throws IOException { + DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes)); + in.readInt(); + in.readUnsignedShort(); + in.readUnsignedShort(); + int constantPoolCount = in.readUnsignedShort(); + + for (int i = 1; i < constantPoolCount; i++) { + int tag = in.readUnsignedByte(); + switch (tag) { + case 1 -> { + String value = in.readUTF(); + String relocated = relocateStringValue(value, dotMappings, slashMappings); + if (!value.equals(relocated)) { + throw new IOException( + "Relocated jar still contains original reference '" + + value + + "' in class entry " + + entryName); + } + } + case 3, 4 -> in.readInt(); + case 5, 6 -> { + in.readLong(); + i++; + } + case 7, 8, 16, 19, 20 -> in.readUnsignedShort(); + case 9, 10, 11, 12, 17, 18 -> { + in.readUnsignedShort(); + in.readUnsignedShort(); + } + case 15 -> { + in.readUnsignedByte(); + in.readUnsignedShort(); + } + default -> + throw new IOException( + "Unknown constant pool tag " + tag + " while validating " + entryName); + } + } + } + + private static String relocateClassPath(String path, Map relocations) { + // Convert path to package format (replacing / with .) + String packagePath = path.substring(0, path.length() - 6).replace('/', '.'); + + // Apply relocations + for (Map.Entry relocation : relocations.entrySet()) { + if (packagePath.startsWith(relocation.getKey())) { + packagePath = relocation.getValue() + packagePath.substring(relocation.getKey().length()); + break; + } } - @Getter - @NonnullByDefault -// Fix the Dependency class to preserve original groupId for downloading - public static final class Dependency { - private final String groupId; - private final String artifactId; - private final String version; - private final String repoUrl; - // Keep the original groupId/artifactId for Maven downloads - private final String originalGroupId; - private final String originalArtifactId; + // Convert back to path format + return packagePath.replace('.', '/') + ".class"; + } - public Dependency(String groupId, String artifactId, String version, String repoUrl) { - this.originalGroupId = Objects.requireNonNull(groupId, "groupId"); - this.originalArtifactId = Objects.requireNonNull(artifactId, "artifactId"); - this.groupId = this.originalGroupId; - this.artifactId = this.originalArtifactId; - this.version = Objects.requireNonNull(version, "version"); - this.repoUrl = Objects.requireNonNull(repoUrl, "repoUrl"); - } - - public URL getUrl() throws MalformedURLException { - String repo = this.repoUrl; - if (!repo.endsWith("/")) { - repo += "/"; - } - repo += "%s/%s/%s/%s-%s.jar"; - - // Always use original groupId for Maven repository URL - String url = String.format(repo, this.originalGroupId.replace(".", "/"), - this.originalArtifactId, this.version, this.originalArtifactId, this.version); - return new URL(url); - } - - // Rest of the class unchanged + private static String relocateResourcePath(String path, Map relocations) { + if (path.startsWith("META-INF/")) { + return path; } + for (Map.Entry relocation : relocations.entrySet()) { + String fromPath = relocation.getKey().replace('.', '/'); + String toPath = relocation.getValue().replace('.', '/'); + if (path.startsWith(fromPath + "/")) { + return toPath + path.substring(fromPath.length()); + } + } + + return path; + } + + private static byte[] readAllBytes(InputStream is) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int bytesRead; + byte[] data = new byte[1024]; + while ((bytesRead = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, bytesRead); + } + return buffer.toByteArray(); + } + + private static void copyStream(InputStream is, OutputStream os) throws IOException { + byte[] buffer = new byte[1024]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + os.write(buffer, 0, bytesRead); + } + } + + private static File getLibFolder() { + File pluginDataFolder = AntiVPN.getInstance().getPluginFolder(); + File libs = new File(pluginDataFolder, "libraries"); + if (libs.mkdirs()) { + System.out.println("Created libraries folder!"); + } + return libs; + } + + @Getter + @NonnullByDefault + // Fix the Dependency class to preserve original groupId for downloading + public static final class Dependency { + private final String groupId; + private final String artifactId; + private final String version; + private final String repoUrl; + // Keep the original groupId/artifactId for Maven downloads + private final String originalGroupId; + private final String originalArtifactId; + + public Dependency(String groupId, String artifactId, String version, String repoUrl) { + this.originalGroupId = Objects.requireNonNull(groupId, "groupId"); + this.originalArtifactId = Objects.requireNonNull(artifactId, "artifactId"); + this.groupId = this.originalGroupId; + this.artifactId = this.originalArtifactId; + this.version = Objects.requireNonNull(version, "version"); + this.repoUrl = Objects.requireNonNull(repoUrl, "repoUrl"); + } + + public URL getUrl() throws MalformedURLException { + String repo = this.repoUrl; + if (!repo.endsWith("/")) { + repo += "/"; + } + repo += "%s/%s/%s/%s-%s.jar"; + + // Always use original groupId for Maven repository URL + String url = + String.format( + repo, + this.originalGroupId.replace(".", "/"), + this.originalArtifactId, + this.version, + this.originalArtifactId, + this.version); + return new URL(url); + } + + // Rest of the class unchanged + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/depends/MavenLibraries.java b/Common/Source/src/main/java/dev/brighten/antivpn/depends/MavenLibraries.java index 4f11cef..dccbe02 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/depends/MavenLibraries.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/depends/MavenLibraries.java @@ -18,14 +18,11 @@ package dev.brighten.antivpn.depends; import java.lang.annotation.*; -/** - * Annotation to indicate the required libraries for a class. - */ +/** Annotation to indicate the required libraries for a class. */ @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MavenLibraries { - MavenLibrary[] value() default {}; - + MavenLibrary[] value() default {}; } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/depends/MavenLibrary.java b/Common/Source/src/main/java/dev/brighten/antivpn/depends/MavenLibrary.java index f2caf49..a672b0a 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/depends/MavenLibrary.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/depends/MavenLibrary.java @@ -18,43 +18,40 @@ package dev.brighten.antivpn.depends; import java.lang.annotation.*; -/** - * Annotation to indicate a required library for a class. - */ +/** Annotation to indicate a required library for a class. */ @Documented @Repeatable(MavenLibraries.class) @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MavenLibrary { - /** - * The group id of the library - * - * @return the group id of the library - */ - String groupId(); + /** + * The group id of the library + * + * @return the group id of the library + */ + String groupId(); - /** - * The artifact id of the library - * - * @return the artifact id of the library - */ - String artifactId(); + /** + * The artifact id of the library + * + * @return the artifact id of the library + */ + String artifactId(); - /** - * The version of the library - * - * @return the version of the library - */ - String version(); + /** + * The version of the library + * + * @return the version of the library + */ + String version(); - /** - * The repo where the library can be obtained from - * - * @return the repo where the library can be obtained from - */ - Repository repo() default @Repository(url = "https://repo1.maven.org/maven2"); - - Relocate[] relocations() default {}; // Add this line + /** + * The repo where the library can be obtained from + * + * @return the repo where the library can be obtained from + */ + Repository repo() default @Repository(url = "https://repo1.maven.org/maven2"); + Relocate[] relocations() default {}; // Add this line } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/depends/Relocate.java b/Common/Source/src/main/java/dev/brighten/antivpn/depends/Relocate.java index 2d4d910..7d00c49 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/depends/Relocate.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/depends/Relocate.java @@ -25,6 +25,7 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({}) public @interface Relocate { - String from(); - String to(); -} \ No newline at end of file + String from(); + + String to(); +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/depends/Repository.java b/Common/Source/src/main/java/dev/brighten/antivpn/depends/Repository.java index 716c291..b310b56 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/depends/Repository.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/depends/Repository.java @@ -18,19 +18,16 @@ package dev.brighten.antivpn.depends; import java.lang.annotation.*; -/** - * Represents a maven repository. - */ +/** Represents a maven repository. */ @Documented @Target(ElementType.LOCAL_VARIABLE) @Retention(RetentionPolicy.RUNTIME) public @interface Repository { - /** - * Gets the base url of the repository. - * - * @return the base url of the repository - */ - String url(); - + /** + * Gets the base url of the repository. + * + * @return the base url of the repository + */ + String url(); } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/depends/URLClassLoaderAccess.java b/Common/Source/src/main/java/dev/brighten/antivpn/depends/URLClassLoaderAccess.java index 1146da8..5f71e64 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/depends/URLClassLoaderAccess.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/depends/URLClassLoaderAccess.java @@ -22,145 +22,140 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.Collection; -/** - * Provides access to {@link URLClassLoader}#addURL. - */ +/** Provides access to {@link URLClassLoader}#addURL. */ public abstract class URLClassLoaderAccess { - /** - * Creates a {@link URLClassLoaderAccess} for the given class loader. - * - * @param classLoader the class loader - * @return the access object - */ - static URLClassLoaderAccess create(URLClassLoader classLoader) { - if (Reflection.isSupported()) { - return new Reflection(classLoader); - } else if (Unsafe.isSupported()) { - return new Unsafe(classLoader); - } else { - return Noop.INSTANCE; - } + /** + * Creates a {@link URLClassLoaderAccess} for the given class loader. + * + * @param classLoader the class loader + * @return the access object + */ + static URLClassLoaderAccess create(URLClassLoader classLoader) { + if (Reflection.isSupported()) { + return new Reflection(classLoader); + } else if (Unsafe.isSupported()) { + return new Unsafe(classLoader); + } else { + return Noop.INSTANCE; + } + } + + private final URLClassLoader classLoader; + + protected URLClassLoaderAccess(URLClassLoader classLoader) { + this.classLoader = classLoader; + } + + /** + * Adds the given URL to the class loader. + * + * @param url the URL to add + */ + public abstract void addURL(URL url); + + /** Accesses using reflection, not supported on Java 9+. */ + private static class Reflection extends URLClassLoaderAccess { + private static final Method ADD_URL_METHOD; + + static { + Method addUrlMethod; + try { + addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + addUrlMethod.setAccessible(true); + } catch (Exception e) { + addUrlMethod = null; + } + ADD_URL_METHOD = addUrlMethod; } - private final URLClassLoader classLoader; - - protected URLClassLoaderAccess(URLClassLoader classLoader) { - this.classLoader = classLoader; + private static boolean isSupported() { + return ADD_URL_METHOD != null; } - - /** - * Adds the given URL to the class loader. - * - * @param url the URL to add - */ - public abstract void addURL(URL url); - - /** - * Accesses using reflection, not supported on Java 9+. - */ - private static class Reflection extends URLClassLoaderAccess { - private static final Method ADD_URL_METHOD; - - static { - Method addUrlMethod; - try { - addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); - addUrlMethod.setAccessible(true); - } catch (Exception e) { - addUrlMethod = null; - } - ADD_URL_METHOD = addUrlMethod; - } - - private static boolean isSupported() { - return ADD_URL_METHOD != null; - } - - Reflection(URLClassLoader classLoader) { - super(classLoader); - } - - @Override - public void addURL(URL url) { - try { - ADD_URL_METHOD.invoke(super.classLoader, url); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } + Reflection(URLClassLoader classLoader) { + super(classLoader); } - /** - * Accesses using sun.misc.Unsafe, supported on Java 9+. - * - * @author Vaishnav Anil (...) - */ - private static class Unsafe extends URLClassLoaderAccess { - private static final sun.misc.Unsafe UNSAFE; + @Override + public void addURL(URL url) { + try { + ADD_URL_METHOD.invoke(super.classLoader, url); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + } - static { - sun.misc.Unsafe unsafe; - try { - Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - unsafe = (sun.misc.Unsafe) unsafeField.get(null); - } catch (Throwable t) { - unsafe = null; - } - UNSAFE = unsafe; - } + /** + * Accesses using sun.misc.Unsafe, supported on Java 9+. + * + * @author Vaishnav Anil (...) + */ + private static class Unsafe extends URLClassLoaderAccess { + private static final sun.misc.Unsafe UNSAFE; - private static boolean isSupported() { - return UNSAFE != null; - } - - private final Collection unopenedURLs; - private final Collection pathURLs; - - @SuppressWarnings("unchecked") - Unsafe(URLClassLoader classLoader) { - super(classLoader); - - Collection unopenedURLs; - Collection pathURLs; - try { - Object ucp = fetchField(URLClassLoader.class, classLoader, "ucp"); - unopenedURLs = (Collection) fetchField(ucp.getClass(), ucp, "unopenedUrls"); - pathURLs = (Collection) fetchField(ucp.getClass(), ucp, "path"); - } catch (Throwable e) { - unopenedURLs = null; - pathURLs = null; - } - this.unopenedURLs = unopenedURLs; - this.pathURLs = pathURLs; - } - - private static Object fetchField(final Class clazz, final Object object, final String name) throws NoSuchFieldException { - Field field = clazz.getDeclaredField(name); - long offset = UNSAFE.objectFieldOffset(field); - return UNSAFE.getObject(object, offset); - } - - @Override - public void addURL(URL url) { - this.unopenedURLs.add(url); - this.pathURLs.add(url); - } + static { + sun.misc.Unsafe unsafe; + try { + Field unsafeField = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + unsafe = (sun.misc.Unsafe) unsafeField.get(null); + } catch (Throwable t) { + unsafe = null; + } + UNSAFE = unsafe; } - private static class Noop extends URLClassLoaderAccess { - private static final Noop INSTANCE = new Noop(); - - private Noop() { - super(null); - } - - @Override - public void addURL(URL url) { - throw new UnsupportedOperationException(); - } + private static boolean isSupported() { + return UNSAFE != null; } + private final Collection unopenedURLs; + private final Collection pathURLs; + + @SuppressWarnings("unchecked") + Unsafe(URLClassLoader classLoader) { + super(classLoader); + + Collection unopenedURLs; + Collection pathURLs; + try { + Object ucp = fetchField(URLClassLoader.class, classLoader, "ucp"); + unopenedURLs = (Collection) fetchField(ucp.getClass(), ucp, "unopenedUrls"); + pathURLs = (Collection) fetchField(ucp.getClass(), ucp, "path"); + } catch (Throwable e) { + unopenedURLs = null; + pathURLs = null; + } + this.unopenedURLs = unopenedURLs; + this.pathURLs = pathURLs; + } + + private static Object fetchField(final Class clazz, final Object object, final String name) + throws NoSuchFieldException { + Field field = clazz.getDeclaredField(name); + long offset = UNSAFE.objectFieldOffset(field); + return UNSAFE.getObject(object, offset); + } + + @Override + public void addURL(URL url) { + this.unopenedURLs.add(url); + this.pathURLs.add(url); + } + } + + private static class Noop extends URLClassLoaderAccess { + private static final Noop INSTANCE = new Noop(); + + private Noop() { + super(null); + } + + @Override + public void addURL(URL url) { + throw new UnsupportedOperationException(); + } + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/message/MessageHandler.java b/Common/Source/src/main/java/dev/brighten/antivpn/message/MessageHandler.java index be1f4e3..ab44697 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/message/MessageHandler.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/message/MessageHandler.java @@ -17,46 +17,51 @@ package dev.brighten.antivpn.message; import dev.brighten.antivpn.AntiVPN; - import java.util.HashMap; import java.util.Map; import java.util.function.Function; public class MessageHandler { - private final Map messages = new HashMap<>(); + private final Map messages = new HashMap<>(); - public VpnString getString(String key) { - if(!messages.containsKey(key)) { - throw new NullPointerException("There is no VpnString with the key \"" + key + "\""); - } - - return messages.get(key); + public VpnString getString(String key) { + if (!messages.containsKey(key)) { + throw new NullPointerException("There is no VpnString with the key \"" + key + "\""); } - public void reloadStrings() { - for (VpnString value : messages.values()) { - value.updateString(); - } - } + return messages.get(key); + } - public void clearStrings() { - messages.clear(); + public void reloadStrings() { + for (VpnString value : messages.values()) { + value.updateString(); } + } - public void addString(VpnString string, Function getter) { - string.setConfigStringGetter(getter); - getter.apply(string); - AntiVPN.getInstance().getExecutor().log("Added string " + string.getKey()); - messages.put(string.getKey(), string); - } + public void clearStrings() { + messages.clear(); + } - public void initStrings(Function getter) { - addString(new VpnString("command-misc-playerRequired", - "&cYou must be a player to execute this command!"), getter); - addString(new VpnString("command-alerts-toggled", - "&7Your player proxy notifications have been set to: &e%state%"), getter); - addString(new VpnString("command-reload-complete", - "&aSuccessfully reloaded KauriVPN plugin!"), getter); - addString(new VpnString("no-permission", "&cNo permission."), getter); - } + public void addString(VpnString string, Function getter) { + string.setConfigStringGetter(getter); + getter.apply(string); + AntiVPN.getInstance().getExecutor().log("Added string " + string.getKey()); + messages.put(string.getKey(), string); + } + + public void initStrings(Function getter) { + addString( + new VpnString( + "command-misc-playerRequired", "&cYou must be a player to execute this command!"), + getter); + addString( + new VpnString( + "command-alerts-toggled", + "&7Your player proxy notifications have been set to: &e%state%"), + getter); + addString( + new VpnString("command-reload-complete", "&aSuccessfully reloaded KauriVPN plugin!"), + getter); + addString(new VpnString("no-permission", "&cNo permission."), getter); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/message/VpnString.java b/Common/Source/src/main/java/dev/brighten/antivpn/message/VpnString.java index 8ea7865..b356411 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/message/VpnString.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/message/VpnString.java @@ -17,58 +17,59 @@ package dev.brighten.antivpn.message; import dev.brighten.antivpn.api.APIPlayer; +import java.util.function.Function; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.SneakyThrows; -import java.util.function.Function; - @Getter public class VpnString { + private final String key; + private final String defaultMessage; + private String message; + @Setter private Function configStringGetter; + + public VpnString(String key, String defaultMessage) { + this.key = key; + this.defaultMessage = defaultMessage; + } + + @SneakyThrows + public void updateString() { + if (configStringGetter == null) + throw new Exception("The configStringGetter for string " + key + " is null!"); + + message = configStringGetter.apply(this); + } + + public String getFormattedMessage(Var... replacements) { + String formatted = configStringGetter.apply(this); + + for (Var replacement : replacements) { + formatted = + formatted.replace( + "%" + replacement.getKey() + "%", replacement.getReplacement().toString()); + } + + return formatted; + } + + public void sendMessage(APIPlayer player, Var... replacements) { + String formatted = message; + + for (Var replacement : replacements) { + formatted = + formatted.replace( + "%" + replacement.getKey() + "%", replacement.getReplacement().toString()); + } + player.sendMessage(formatted); + } + + @Getter + @RequiredArgsConstructor + public static class Var { private final String key; - private final String defaultMessage; - private String message; - @Setter - private Function configStringGetter; - - public VpnString(String key, String defaultMessage) { - this.key = key; - this.defaultMessage = defaultMessage; - } - - @SneakyThrows - public void updateString() { - if(configStringGetter == null) throw new Exception("The configStringGetter for string " + key + " is null!"); - - message = configStringGetter.apply(this); - } - - public String getFormattedMessage(Var... replacements) { - String formatted = configStringGetter.apply(this); - - for (Var replacement : replacements) { - formatted = formatted - .replace("%" + replacement.getKey() + "%", replacement.getReplacement().toString()); - } - - return formatted; - } - - public void sendMessage(APIPlayer player, Var... replacements) { - String formatted = message; - - for (Var replacement : replacements) { - formatted = formatted - .replace("%" + replacement.getKey() + "%", replacement.getReplacement().toString()); - } - player.sendMessage(formatted); - } - - @Getter - @RequiredArgsConstructor - public static class Var { - private final String key; - private final Object replacement; - } + private final Object replacement; + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/CIDRUtils.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/CIDRUtils.java index 94cb251..d72ad55 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/CIDRUtils.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/CIDRUtils.java @@ -16,116 +16,106 @@ package dev.brighten.antivpn.utils; -import lombok.Getter; - import java.math.BigInteger; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import lombok.Getter; /** - * A class that enables to get an IP range from CIDR specification. It supports - * both IPv4 and IPv6. + * A class that enables to get an IP range from CIDR specification. It supports both IPv4 and IPv6. */ @Getter public class CIDRUtils { - private final String cidr; + private final String cidr; - private final InetAddress inetAddress; + private final InetAddress inetAddress; - private InetAddress startAddress; - private BigInteger startIpInt, endIpInt; - private InetAddress endAddress; - private final int prefixLength; + private InetAddress startAddress; + private BigInteger startIpInt, endIpInt; + private InetAddress endAddress; + private final int prefixLength; + public CIDRUtils(String cidr) throws UnknownHostException { - public CIDRUtils(String cidr) throws UnknownHostException { + this.cidr = cidr; - this.cidr = cidr; + /* split CIDR to address and prefix part */ + if (this.cidr.contains("/")) { + int index = this.cidr.indexOf('/'); + String addressPart = this.cidr.substring(0, index); + String networkPart = this.cidr.substring(index + 1); - /* split CIDR to address and prefix part */ - if (this.cidr.contains("/")) { - int index = this.cidr.indexOf("/"); - String addressPart = this.cidr.substring(0, index); - String networkPart = this.cidr.substring(index + 1); + inetAddress = InetAddress.getByName(addressPart); + prefixLength = Integer.parseInt(networkPart); - inetAddress = InetAddress.getByName(addressPart); - prefixLength = Integer.parseInt(networkPart); + calculate(); + } else { + throw new IllegalArgumentException("not an valid CIDR format!"); + } + } - calculate(); - } else { - throw new IllegalArgumentException("not an valid CIDR format!"); - } + private void calculate() throws UnknownHostException { + + ByteBuffer maskBuffer; + int targetSize; + if (inetAddress.getAddress().length == 4) { + maskBuffer = ByteBuffer.allocate(4).putInt(-1); + targetSize = 4; + } else { + maskBuffer = ByteBuffer.allocate(16).putLong(-1L).putLong(-1L); + targetSize = 16; } + BigInteger mask = (new BigInteger(1, maskBuffer.array())).not().shiftRight(prefixLength); - private void calculate() throws UnknownHostException { + ByteBuffer buffer = ByteBuffer.wrap(inetAddress.getAddress()); + BigInteger ipVal = new BigInteger(1, buffer.array()); - ByteBuffer maskBuffer; - int targetSize; - if (inetAddress.getAddress().length == 4) { - maskBuffer = - ByteBuffer - .allocate(4) - .putInt(-1); - targetSize = 4; - } else { - maskBuffer = ByteBuffer.allocate(16) - .putLong(-1L) - .putLong(-1L); - targetSize = 16; - } + BigInteger startIp = ipVal.and(mask); + this.startIpInt = startIp; + BigInteger endIp = startIp.add(mask.not()); + this.endIpInt = endIp; - BigInteger mask = (new BigInteger(1, maskBuffer.array())).not().shiftRight(prefixLength); + byte[] startIpArr = toBytes(startIp.toByteArray(), targetSize); + byte[] endIpArr = toBytes(endIp.toByteArray(), targetSize); - ByteBuffer buffer = ByteBuffer.wrap(inetAddress.getAddress()); - BigInteger ipVal = new BigInteger(1, buffer.array()); - - BigInteger startIp = ipVal.and(mask); - this.startIpInt = startIp; - BigInteger endIp = startIp.add(mask.not()); - this.endIpInt = endIp; - - byte[] startIpArr = toBytes(startIp.toByteArray(), targetSize); - byte[] endIpArr = toBytes(endIp.toByteArray(), targetSize); - - this.startAddress = InetAddress.getByAddress(startIpArr); - this.endAddress = InetAddress.getByAddress(endIpArr); + this.startAddress = InetAddress.getByAddress(startIpArr); + this.endAddress = InetAddress.getByAddress(endIpArr); + } + private byte[] toBytes(byte[] array, int targetSize) { + int counter = 0; + List newArr = new ArrayList(); + while (counter < targetSize && (array.length - 1 - counter >= 0)) { + newArr.add(0, array[array.length - 1 - counter]); + counter++; } - private byte[] toBytes(byte[] array, int targetSize) { - int counter = 0; - List newArr = new ArrayList(); - while (counter < targetSize && (array.length - 1 - counter >= 0)) { - newArr.add(0, array[array.length - 1 - counter]); - counter++; - } + int size = newArr.size(); + for (int i = 0; i < (targetSize - size); i++) { - int size = newArr.size(); - for (int i = 0; i < (targetSize - size); i++) { - - newArr.add(0, (byte) 0); - } - - byte[] ret = new byte[newArr.size()]; - for (int i = 0; i < newArr.size(); i++) { - ret[i] = newArr.get(i); - } - return ret; + newArr.add(0, (byte) 0); } - public boolean isInRange(String ipAddress) throws UnknownHostException { - InetAddress address = InetAddress.getByName(ipAddress); - BigInteger start = new BigInteger(1, this.startAddress.getAddress()); - BigInteger end = new BigInteger(1, this.endAddress.getAddress()); - BigInteger target = new BigInteger(1, address.getAddress()); - - int st = start.compareTo(target); - int te = target.compareTo(end); - - return (st < 0 || st == 0) && (te < 0 || te == 0); + byte[] ret = new byte[newArr.size()]; + for (int i = 0; i < newArr.size(); i++) { + ret[i] = newArr.get(i); } -} \ No newline at end of file + return ret; + } + + public boolean isInRange(String ipAddress) throws UnknownHostException { + InetAddress address = InetAddress.getByName(ipAddress); + BigInteger start = new BigInteger(1, this.startAddress.getAddress()); + BigInteger end = new BigInteger(1, this.endAddress.getAddress()); + BigInteger target = new BigInteger(1, address.getAddress()); + + int st = start.compareTo(target); + int te = target.compareTo(end); + + return (st < 0 || st == 0) && (te < 0 || te == 0); + } +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/ConfigDefault.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/ConfigDefault.java index 5fe30e7..b79d6a9 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/ConfigDefault.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/ConfigDefault.java @@ -22,23 +22,22 @@ import lombok.AllArgsConstructor; @AllArgsConstructor public class ConfigDefault { - private final A defaultValue; - private final String path; - private final AntiVPN plugin; + private final A defaultValue; + private final String path; + private final AntiVPN plugin; - public A get() { - if(plugin.getConfig().get(path) != null) - return (A) plugin.getConfig().get(path); - else { - plugin.getConfig().set(path, defaultValue); - plugin.saveConfig(); - return defaultValue; - } + public A get() { + if (plugin.getConfig().get(path) != null) return (A) plugin.getConfig().get(path); + else { + plugin.getConfig().set(path, defaultValue); + plugin.saveConfig(); + return defaultValue; } + } - public A set(A value) { - plugin.getConfig().set(path, value); + public A set(A value) { + plugin.getConfig().set(path, value); - return value; - } + return value; + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/EvictingMap.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/EvictingMap.java index 46bacc6..97a420d 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/EvictingMap.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/EvictingMap.java @@ -16,20 +16,18 @@ package dev.brighten.antivpn.utils; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - import java.util.LinkedHashMap; import java.util.Map; +import lombok.Getter; +import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class EvictingMap extends LinkedHashMap { - @Getter - private final int size; + @Getter private final int size; - @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() >= size; - } + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() >= size; + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/IpUtils.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/IpUtils.java index bb982ec..19f0329 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/IpUtils.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/IpUtils.java @@ -25,85 +25,87 @@ import java.net.UnknownHostException; import java.util.Optional; public class IpUtils { - public static Optional getIpDecimal(String address) { - try { - InetAddress inet = InetAddress.getByName(address); + public static Optional getIpDecimal(String address) { + try { + InetAddress inet = InetAddress.getByName(address); - if(inet instanceof Inet4Address) { - return Optional.of(BigDecimal.valueOf(ipv4ToLong(address))); - } return Optional.of(new BigDecimal(ipv6ToDecimalFormat(address))); - } catch(Exception e) { - return Optional.empty(); - } + if (inet instanceof Inet4Address) { + return Optional.of(BigDecimal.valueOf(ipv4ToLong(address))); + } + return Optional.of(new BigDecimal(ipv6ToDecimalFormat(address))); + } catch (Exception e) { + return Optional.empty(); + } + } + + public static long ipv4ToLong(String address) { + String[] addrArray = address.split("\\."); + + long ipDecimal = 0; + + for (int i = 0; i < addrArray.length; i++) { + + int power = 3 - i; + ipDecimal += ((Integer.parseInt(addrArray[i]) % 256 * Math.pow(256, power))); } - public static long ipv4ToLong(String address) { - String[] addrArray = address.split("\\."); + return ipDecimal; + } - long ipDecimal = 0; + public static String getIpv4(long ip) { + StringBuilder sb = new StringBuilder(15); - for (int i = 0; i < addrArray.length; i++) { + for (int i = 0; i < 4; i++) { + sb.insert(0, ip & 0xff); - int power = 3 - i; - ipDecimal += ((Integer.parseInt(addrArray[i]) % 256 * Math.pow(256, power))); - } + if (i < 3) { + sb.insert(0, '.'); + } - return ipDecimal; + ip >>= 8; } - public static String getIpv4(long ip) { - StringBuilder sb = new StringBuilder(15); + return sb.toString(); + } - for (int i = 0; i < 4; i++) { - sb.insert(0, ip & 0xff); + public static boolean isIpv4(BigDecimal ip) { + return ip.compareTo(BigDecimal.valueOf(4294967295L)) <= 0; + } - if (i < 3) { - sb.insert(0, '.'); - } + public static boolean isIpv6(BigDecimal ip) { + return ip.compareTo(BigDecimal.valueOf(4294967295L)) > 0; + } - ip >>= 8; - } + public static boolean isIpv4(String ip) { + return ip.matches("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$"); + } - return sb.toString(); + public static boolean isNotIp(String ip) { + return !isIpv4(ip) && !isIpv6(ip); + } + + public static boolean isIpv6(String ip) { + return ip.matches( + "^([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:)$|^(([0-9a-fA-F]{1,4}:){0,6}([0-9a-fA-F]{1,4}|:))?(::([0-9a-fA-F]{1,4}:){0,5}([0-9a-fA-F]{1,4}|:))?$"); + } + + public static String getIpv4(BigDecimal ip) { + try { + return Inet4Address.getByAddress(ip.toBigInteger().toByteArray()).getHostAddress(); + } catch (UnknownHostException e) { + return "Error"; } + } - public static boolean isIpv4(BigDecimal ip) { - return ip.compareTo(BigDecimal.valueOf(4294967295L)) <= 0; - } - - public static boolean isIpv6(BigDecimal ip) { - return ip.compareTo(BigDecimal.valueOf(4294967295L)) > 0; - } - public static boolean isIpv4(String ip) { - return ip.matches("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$"); - } - - public static boolean isNotIp(String ip) { - return !isIpv4(ip) && !isIpv6(ip); - } - - public static boolean isIpv6(String ip) { - return ip.matches("^([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]{1,4}|:)$|^(([0-9a-fA-F]{1,4}:){0,6}([0-9a-fA-F]{1,4}|:))?(::([0-9a-fA-F]{1,4}:){0,5}([0-9a-fA-F]{1,4}|:))?$"); - } - - public static String getIpv4(BigDecimal ip) { - try { - return Inet4Address.getByAddress(ip.toBigInteger().toByteArray()).getHostAddress(); - } catch (UnknownHostException e) { - return "Error"; - } - } - - public static String getIpv6(BigDecimal ip) { - try { - return Inet6Address.getByAddress(ip.toBigInteger().toByteArray()).getHostAddress(); - } catch (UnknownHostException e) { - return "Error"; - } - } - - public static BigInteger ipv6ToDecimalFormat(String ipAddress) throws UnknownHostException { - return new BigInteger(1, Inet6Address.getByName(ipAddress).getAddress()); + public static String getIpv6(BigDecimal ip) { + try { + return Inet6Address.getByAddress(ip.toBigInteger().toByteArray()).getHostAddress(); + } catch (UnknownHostException e) { + return "Error"; } + } + public static BigInteger ipv6ToDecimalFormat(String ipAddress) throws UnknownHostException { + return new BigInteger(1, Inet6Address.getByName(ipAddress).getAddress()); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/MiscUtils.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/MiscUtils.java index 4daf11d..201c97f 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/MiscUtils.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/MiscUtils.java @@ -20,7 +20,6 @@ import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.utils.json.JSONException; import dev.brighten.antivpn.utils.json.JSONObject; import dev.brighten.antivpn.utils.json.JsonReader; - import java.io.*; import java.math.BigInteger; import java.net.*; @@ -32,164 +31,177 @@ import java.util.regex.Pattern; public class MiscUtils { - private static final Pattern ipv4 = Pattern.compile("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}"); - private static final String DEFAULT_FUNKEMUNKY_UUID_ENDPOINT = "https://funkemunky.cc/mojang/uuid?name="; - private static final String DEFAULT_MOJANG_UUID_ENDPOINT = "https://api.mojang.com/users/profiles/minecraft/"; - private static volatile String funkemunkyUuidEndpoint = DEFAULT_FUNKEMUNKY_UUID_ENDPOINT; - private static volatile String mojangUuidEndpoint = DEFAULT_MOJANG_UUID_ENDPOINT; + private static final Pattern ipv4 = + Pattern.compile("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}"); + private static final String DEFAULT_FUNKEMUNKY_UUID_ENDPOINT = + "https://funkemunky.cc/mojang/uuid?name="; + private static final String DEFAULT_MOJANG_UUID_ENDPOINT = + "https://api.mojang.com/users/profiles/minecraft/"; + private static volatile String funkemunkyUuidEndpoint = DEFAULT_FUNKEMUNKY_UUID_ENDPOINT; + private static volatile String mojangUuidEndpoint = DEFAULT_MOJANG_UUID_ENDPOINT; - public static void close(Closeable... closeables) { - try { - for (Closeable closeable : closeables) if (closeable != null) closeable.close(); - } catch (Exception e) { - AntiVPN.getInstance().getExecutor().logException(e); - } + public static void close(Closeable... closeables) { + try { + for (Closeable closeable : closeables) if (closeable != null) closeable.close(); + } catch (Exception e) { + AntiVPN.getInstance().getExecutor().logException(e); + } + } + + public static void close(AutoCloseable... closeables) { + try { + for (AutoCloseable closeable : closeables) if (closeable != null) closeable.close(); + } catch (Exception e) { + AntiVPN.getInstance().getExecutor().logException(e); + } + } + + public static void copy(InputStream in, File file) { + try { + OutputStream out = new FileOutputStream(file); + int lenght; + byte[] buf = new byte[1024]; + + while ((lenght = in.read(buf)) > 0) { + out.write(buf, 0, lenght); + } + + out.close(); + in.close(); + } catch (Exception e) { + AntiVPN.getInstance().getExecutor().logException(e); + } + } + + public static ThreadFactory createThreadFactory(String threadName) { + return r -> { + Thread thread = new Thread(r); + thread.setName(threadName); + return thread; + }; + } + + public static List rangeToCidrs(BigInteger start, BigInteger end) + throws UnknownHostException { + List cidrs = new ArrayList<>(); + + while (start.compareTo(end) <= 0) { + // Find the number of trailing zero bits — this determines max block size alignment + int trailingZeros = + start.equals(BigInteger.ZERO) + ? 128 // handle the edge case + : start.getLowestSetBit(); + + // Find the largest block that fits + BigInteger remaining = end.subtract(start).add(BigInteger.ONE); + int maxBits = remaining.bitLength() - 1; + + int blockBits = Math.min(trailingZeros, maxBits); + int prefixLen = 32 - blockBits; // use 128 for IPv6 + + // Build the CIDR string + byte[] addrBytes = toFixedLengthBytes(start); // use 16 for IPv6 + String cidr = InetAddress.getByAddress(addrBytes).getHostAddress() + "/" + prefixLen; + cidrs.add(new CIDRUtils(cidr)); + + // Advance past this block + start = start.add(BigInteger.ONE.shiftLeft(blockBits)); } - public static void close(AutoCloseable... closeables) { - try { - for (AutoCloseable closeable : closeables) if (closeable != null) closeable.close(); - } catch (Exception e) { - AntiVPN.getInstance().getExecutor().logException(e); - } + return cidrs; + } + + private static byte[] toFixedLengthBytes(BigInteger value) { + byte[] raw = value.toByteArray(); + byte[] result = new byte[4]; + int srcPos = Math.max(0, raw.length - 4); + int destPos = Math.max(0, 4 - raw.length); + System.arraycopy(raw, srcPos, result, destPos, Math.min(raw.length, 4)); + return result; + } + + public static UUID lookupUUID(String playername) { + try { + UUID uuid = lookupUuidFromUrl(funkemunkyUuidEndpoint + playername); + if (uuid != null) { + return uuid; + } + } catch (IOException | JSONException | URISyntaxException e) { + AntiVPN.getInstance() + .getExecutor() + .logException( + "Error while looking up UUID for " + playername + "! Falling back to Mojang API", e); + return lookupMojangUuid(playername); } - public static void copy(InputStream in, File file) { - try { - OutputStream out = new FileOutputStream(file); - int lenght; - byte[] buf = new byte[1024]; + return null; + } - while ((lenght = in.read(buf)) > 0) - { - out.write(buf, 0, lenght); - } + private static UUID lookupUuidFromUrl(String url) + throws IOException, JSONException, URISyntaxException { + HttpURLConnection connection = (HttpURLConnection) new URI(url).toURL().openConnection(); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + connection.setInstanceFollowRedirects(true); - out.close(); - in.close(); - } catch (Exception e) { - AntiVPN.getInstance().getExecutor().logException(e); - } + int responseCode = connection.getResponseCode(); + if (responseCode >= 500) { + throw new IOException("Server returned HTTP " + responseCode + " for " + url); + } + if (responseCode != HttpURLConnection.HTTP_OK) { + return null; } - public static ThreadFactory createThreadFactory(String threadName) { - return r -> { - Thread thread = new Thread(r); - thread.setName(threadName); - return thread; - }; + try (InputStream inputStream = connection.getInputStream()) { + JSONObject object = + new JSONObject( + JsonReader.readAll( + new InputStreamReader(inputStream, java.nio.charset.StandardCharsets.UTF_8))); + if (object.has("uuid")) { + return parseUuid(object.getString("uuid")); + } + if (object.has("id")) { + return parseUuid(object.getString("id")); + } } - public static List rangeToCidrs(BigInteger start, BigInteger end) throws UnknownHostException { - List cidrs = new ArrayList<>(); + return null; + } - while (start.compareTo(end) <= 0) { - // Find the number of trailing zero bits — this determines max block size alignment - int trailingZeros = start.equals(BigInteger.ZERO) - ? 128 // handle the edge case - : start.getLowestSetBit(); - - // Find the largest block that fits - BigInteger remaining = end.subtract(start).add(BigInteger.ONE); - int maxBits = remaining.bitLength() - 1; - - int blockBits = Math.min(trailingZeros, maxBits); - int prefixLen = 32 - blockBits; // use 128 for IPv6 - - // Build the CIDR string - byte[] addrBytes = toFixedLengthBytes(start); // use 16 for IPv6 - String cidr = InetAddress.getByAddress(addrBytes).getHostAddress() + "/" + prefixLen; - cidrs.add(new CIDRUtils(cidr)); - - // Advance past this block - start = start.add(BigInteger.ONE.shiftLeft(blockBits)); - } - - return cidrs; + private static UUID parseUuid(String value) { + if (value.length() == 32) { + value = + value.replaceFirst( + "([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{12})", + "$1-$2-$3-$4-$5"); } - private static byte[] toFixedLengthBytes(BigInteger value) { - byte[] raw = value.toByteArray(); - byte[] result = new byte[4]; - int srcPos = Math.max(0, raw.length - 4); - int destPos = Math.max(0, 4 - raw.length); - System.arraycopy(raw, srcPos, result, destPos, Math.min(raw.length, 4)); - return result; + return UUID.fromString(value); + } + + private static UUID lookupMojangUuid(String playerName) { + try { + return lookupUuidFromUrl(mojangUuidEndpoint + playerName); + } catch (IOException | JSONException | URISyntaxException e) { + AntiVPN.getInstance() + .getExecutor() + .logException("Error while looking up UUID for " + playerName + " from Mojang!:", e); } - public static UUID lookupUUID(String playername) { - try { - UUID uuid = lookupUuidFromUrl(funkemunkyUuidEndpoint + playername); - if (uuid != null) { - return uuid; - } - } catch (IOException | JSONException | URISyntaxException e) { - AntiVPN.getInstance().getExecutor().logException("Error while looking up UUID for " + playername + "! Falling back to Mojang API", e); - return lookupMojangUuid(playername); - } + return null; + } - return null; - } + static void setLookupEndpointsForTesting(String funkemunkyEndpoint, String mojangEndpoint) { + funkemunkyUuidEndpoint = funkemunkyEndpoint; + mojangUuidEndpoint = mojangEndpoint; + } - private static UUID lookupUuidFromUrl(String url) throws IOException, JSONException, URISyntaxException { - HttpURLConnection connection = (HttpURLConnection) new URI(url).toURL().openConnection(); - connection.setConnectTimeout(5000); - connection.setReadTimeout(5000); - connection.setInstanceFollowRedirects(true); + static void resetLookupEndpointsForTesting() { + funkemunkyUuidEndpoint = DEFAULT_FUNKEMUNKY_UUID_ENDPOINT; + mojangUuidEndpoint = DEFAULT_MOJANG_UUID_ENDPOINT; + } - int responseCode = connection.getResponseCode(); - if (responseCode >= 500) { - throw new IOException("Server returned HTTP " + responseCode + " for " + url); - } - if (responseCode != HttpURLConnection.HTTP_OK) { - return null; - } - - try (InputStream inputStream = connection.getInputStream()) { - JSONObject object = new JSONObject(JsonReader.readAll(new InputStreamReader(inputStream, java.nio.charset.StandardCharsets.UTF_8))); - if (object.has("uuid")) { - return parseUuid(object.getString("uuid")); - } - if (object.has("id")) { - return parseUuid(object.getString("id")); - } - } - - return null; - } - - private static UUID parseUuid(String value) { - if (value.length() == 32) { - value = value.replaceFirst( - "([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{12})", - "$1-$2-$3-$4-$5" - ); - } - - return UUID.fromString(value); - } - - private static UUID lookupMojangUuid(String playerName) { - try { - return lookupUuidFromUrl(mojangUuidEndpoint + playerName); - } catch (IOException | JSONException | URISyntaxException e) { - AntiVPN.getInstance().getExecutor().logException("Error while looking up UUID for " + playerName + " from Mojang!:", e); - } - - return null; - } - - static void setLookupEndpointsForTesting(String funkemunkyEndpoint, String mojangEndpoint) { - funkemunkyUuidEndpoint = funkemunkyEndpoint; - mojangUuidEndpoint = mojangEndpoint; - } - - static void resetLookupEndpointsForTesting() { - funkemunkyUuidEndpoint = DEFAULT_FUNKEMUNKY_UUID_ENDPOINT; - mojangUuidEndpoint = DEFAULT_MOJANG_UUID_ENDPOINT; - } - public static boolean isIpv4(String ip) - { - return ipv4.matcher(ip).matches(); - } + public static boolean isIpv4(String ip) { + return ipv4.matcher(ip).matches(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/NonnullByDefault.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/NonnullByDefault.java index 33d759f..c551673 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/NonnullByDefault.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/NonnullByDefault.java @@ -22,6 +22,4 @@ import java.lang.annotation.RetentionPolicy; @Documented @Retention(RetentionPolicy.RUNTIME) -public @interface NonnullByDefault { - -} +public @interface NonnullByDefault {} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/Preconditions.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/Preconditions.java index ba96d8b..7e34961 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/Preconditions.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/Preconditions.java @@ -22,239 +22,241 @@ package dev.brighten.antivpn.utils; public final class Preconditions { - private Preconditions() { + private Preconditions() {} + + public static T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } else { + return reference; + } + } + + public static T checkNotNull(T reference, Object errorMessage) { + if (reference == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } else { + return reference; + } + } + + public static T checkNotNull( + T reference, String errorMessageTemplate, Object... errorMessageArgs) { + if (reference == null) { + throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs)); + } else { + return reference; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, char p1) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, int p1) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, long p1) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, Object p1) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, char p1, char p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, char p1, int p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, char p1, long p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, char p1, Object p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, int p1, char p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, int p1, int p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, int p1, long p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, int p1, Object p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, long p1, char p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, long p1, int p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, long p1, long p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, long p1, Object p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, char p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, int p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, long p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, Object p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } else { + return obj; + } + } + + public static T checkNotNull( + T obj, String errorMessageTemplate, Object p1, Object p2, Object p3) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3)); + } else { + return obj; + } + } + + public static T checkNotNull( + T obj, String errorMessageTemplate, Object p1, Object p2, Object p3, Object p4) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3, p4)); + } else { + return obj; + } + } + + static String format(String template, Object... args) { + template = String.valueOf(template); + StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); + int templateStart = 0; + + int i; + int placeholderStart; + for (i = 0; i < args.length; templateStart = placeholderStart + 2) { + placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + + builder.append(template, templateStart, placeholderStart); + builder.append(args[i++]); } - public static T checkNotNull(T reference) { - if (reference == null) { - throw new NullPointerException(); - } else { - return reference; - } + builder.append(template, templateStart, template.length()); + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + + builder.append(']'); } - public static T checkNotNull(T reference, Object errorMessage) { - if (reference == null) { - throw new NullPointerException(String.valueOf(errorMessage)); - } else { - return reference; - } - } - - public static T checkNotNull(T reference, String errorMessageTemplate, Object... errorMessageArgs) { - if (reference == null) { - throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs)); - } else { - return reference; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, char p1) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, int p1) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, long p1) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, Object p1) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, char p1, char p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, char p1, int p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, char p1, long p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, char p1, Object p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, int p1, char p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, int p1, int p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, int p1, long p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, int p1, Object p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, long p1, char p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, long p1, int p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, long p1, long p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, long p1, Object p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, char p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, int p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, long p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, Object p2) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, Object p2, Object p3) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3)); - } else { - return obj; - } - } - - public static T checkNotNull(T obj, String errorMessageTemplate, Object p1, Object p2, Object p3, Object p4) { - if (obj == null) { - throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3, p4)); - } else { - return obj; - } - } - - static String format(String template, Object... args) { - template = String.valueOf(template); - StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); - int templateStart = 0; - - int i; - int placeholderStart; - for(i = 0; i < args.length; templateStart = placeholderStart + 2) { - placeholderStart = template.indexOf("%s", templateStart); - if (placeholderStart == -1) { - break; - } - - builder.append(template, templateStart, placeholderStart); - builder.append(args[i++]); - } - - builder.append(template, templateStart, template.length()); - if (i < args.length) { - builder.append(" ["); - builder.append(args[i++]); - - while(i < args.length) { - builder.append(", "); - builder.append(args[i++]); - } - - builder.append(']'); - } - - return builder.toString(); - } + return builder.toString(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/StringUtil.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/StringUtil.java index 2fe79fe..7786702 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/StringUtil.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/StringUtil.java @@ -20,31 +20,34 @@ import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.web.objects.VPNResponse; public class StringUtil { - public static String line(String color) { - return color + "&m-----------------------------------------------------"; + public static String line(String color) { + return color + "&m-----------------------------------------------------"; + } + + public static String line() { + return "&m-----------------------------------------------------"; + } + + public static String varReplace(String input, APIPlayer player, VPNResponse result) { + return translateAlternateColorCodes( + '&', + input + .replace("%player%", player.getName()) + .replace("%reason%", result.getMethod()) + .replace("%country%", result.getCountryName()) + .replace("%city%", result.getCity())); + } + + public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) { + char[] b = textToTranslate.toCharArray(); + + for (int i = 0; i < b.length - 1; ++i) { + if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i + 1]) > -1) { + b[i] = 167; + b[i + 1] = Character.toLowerCase(b[i + 1]); + } } - public static String line() { - return "&m-----------------------------------------------------"; - } - - public static String varReplace(String input, APIPlayer player, VPNResponse result) { - return translateAlternateColorCodes('&', input.replace("%player%", player.getName()) - .replace("%reason%", result.getMethod()) - .replace("%country%", result.getCountryName()) - .replace("%city%", result.getCity())); - } - - public static String translateAlternateColorCodes(char altColorChar, String textToTranslate) { - char[] b = textToTranslate.toCharArray(); - - for(int i = 0; i < b.length - 1; ++i) { - if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i + 1]) > -1) { - b[i] = 167; - b[i + 1] = Character.toLowerCase(b[i + 1]); - } - } - - return new String(b); - } + return new String(b); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/Supplier.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/Supplier.java index 159386e..f79ea32 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/Supplier.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/Supplier.java @@ -23,5 +23,5 @@ package dev.brighten.antivpn.utils; @FunctionalInterface public interface Supplier extends java.util.function.Supplier { - T get(); + T get(); } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/Suppliers.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/Suppliers.java index 531a939..3b076bc 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/Suppliers.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/Suppliers.java @@ -16,13 +16,13 @@ package dev.brighten.antivpn.utils; -import java.io.Serial; -import java.io.Serializable; - import static dev.brighten.antivpn.utils.NullnessCasts.uncheckedCastNullableTToT; import static dev.brighten.antivpn.utils.Preconditions.checkNotNull; import static java.util.Objects.requireNonNull; +import java.io.Serial; +import java.io.Serializable; + /** * Useful suppliers. * @@ -33,115 +33,114 @@ import static java.util.Objects.requireNonNull; * @since 2.0 */ public final class Suppliers { - private Suppliers() {} + private Suppliers() {} - /** - * Returns a supplier which caches the instance retrieved during the first call to {@code get()} - * and returns that value on later calls to {@code get()}. See: memoization - * - *

The returned supplier is thread-safe. The delegate's {@code get()} method will be invoked at - * most once unless the underlying {@code get()} throws an exception. The supplier's serialized - * form does not contain the cached value, which will be recalculated when {@code get()} is called - * on the reserialized instance. - * - *

When the underlying delegate throws an exception then this memorizing supplier will keep - * delegating calls until it returns valid data. - * - *

If {@code delegate} is an instance created by an earlier call to {@code memoize}, it is - * returned directly. - */ - public static Supplier memoize(Supplier delegate) { - if (delegate instanceof NonSerializableMemoizingSupplier - || delegate instanceof MemoizingSupplier) { - return delegate; - } - return delegate instanceof Serializable - ? new MemoizingSupplier<>(delegate) - : new NonSerializableMemoizingSupplier<>(delegate); + /** + * Returns a supplier which caches the instance retrieved during the first call to {@code get()} + * and returns that value on later calls to {@code get()}. See: memoization + * + *

The returned supplier is thread-safe. The delegate's {@code get()} method will be invoked at + * most once unless the underlying {@code get()} throws an exception. The supplier's serialized + * form does not contain the cached value, which will be recalculated when {@code get()} is called + * on the reserialized instance. + * + *

When the underlying delegate throws an exception then this memorizing supplier will keep + * delegating calls until it returns valid data. + * + *

If {@code delegate} is an instance created by an earlier call to {@code memoize}, it is + * returned directly. + */ + public static Supplier memoize(Supplier delegate) { + if (delegate instanceof NonSerializableMemoizingSupplier + || delegate instanceof MemoizingSupplier) { + return delegate; + } + return delegate instanceof Serializable + ? new MemoizingSupplier<>(delegate) + : new NonSerializableMemoizingSupplier<>(delegate); + } + + static class MemoizingSupplier implements Supplier, Serializable { + final Supplier delegate; + transient volatile boolean initialized; + // "value" does not need to be volatile; visibility piggy-backs + // on volatile read of "initialized". + transient T value; + + MemoizingSupplier(Supplier delegate) { + this.delegate = checkNotNull(delegate); } - static class MemoizingSupplier implements Supplier, Serializable { - final Supplier delegate; - transient volatile boolean initialized; - // "value" does not need to be volatile; visibility piggy-backs - // on volatile read of "initialized". - transient T value; - - MemoizingSupplier(Supplier delegate) { - this.delegate = checkNotNull(delegate); + @Override + public T get() { + // A 2-field variant of Double Checked Locking. + if (!initialized) { + synchronized (this) { + if (!initialized) { + T t = delegate.get(); + value = t; + initialized = true; + return t; + } } - - @Override - public T get() { - // A 2-field variant of Double Checked Locking. - if (!initialized) { - synchronized (this) { - if (!initialized) { - T t = delegate.get(); - value = t; - initialized = true; - return t; - } - } - } - // This is safe because we checked `initialized.` - return uncheckedCastNullableTToT(value); - } - - @Override - public String toString() { - return "Suppliers.memoize(" - + (initialized ? "" : delegate) - + ")"; - } - - @Serial - private static final long serialVersionUID = 0; + } + // This is safe because we checked `initialized.` + return uncheckedCastNullableTToT(value); } - static class NonSerializableMemoizingSupplier implements Supplier { - volatile Supplier delegate; - volatile boolean initialized; - // "value" does not need to be volatile; visibility piggy-backs - // on volatile read of "initialized". - T value; - - NonSerializableMemoizingSupplier(Supplier delegate) { - this.delegate = checkNotNull(delegate); - } - - @Override - public T get() { - // A 2-field variant of Double Checked Locking. - if (!initialized) { - synchronized (this) { - if (!initialized) { - /* - * requireNonNull is safe because we read and write `delegate` under synchronization. - * - * TODO(cpovirk): To avoid having to check for null, replace `delegate` with a singleton - * `Supplier` that always throws an exception. - */ - T t = requireNonNull(delegate).get(); - value = t; - initialized = true; - // Release the delegate to GC. - delegate = null; - return t; - } - } - } - // This is safe because we checked `initialized.` - return uncheckedCastNullableTToT(value); - } - - @Override - public String toString() { - Supplier delegate = this.delegate; - return "Suppliers.memoize(" - + (delegate == null ? "" : delegate) - + ")"; - } + @Override + public String toString() { + return "Suppliers.memoize(" + + (initialized ? "" : delegate) + + ")"; } + + @Serial private static final long serialVersionUID = 0; + } + + static class NonSerializableMemoizingSupplier implements Supplier { + volatile Supplier delegate; + volatile boolean initialized; + // "value" does not need to be volatile; visibility piggy-backs + // on volatile read of "initialized". + T value; + + NonSerializableMemoizingSupplier(Supplier delegate) { + this.delegate = checkNotNull(delegate); + } + + @Override + public T get() { + // A 2-field variant of Double Checked Locking. + if (!initialized) { + synchronized (this) { + if (!initialized) { + /* + * requireNonNull is safe because we read and write `delegate` under synchronization. + * + * TODO(cpovirk): To avoid having to check for null, replace `delegate` with a singleton + * `Supplier` that always throws an exception. + */ + T t = requireNonNull(delegate).get(); + value = t; + initialized = true; + // Release the delegate to GC. + delegate = null; + return t; + } + } + } + // This is safe because we checked `initialized.` + return uncheckedCastNullableTToT(value); + } + + @Override + public String toString() { + Supplier delegate = this.delegate; + return "Suppliers.memoize(" + + (delegate == null ? "" : delegate) + + ")"; + } + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/Tuple.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/Tuple.java index 1e650c6..eac90be 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/Tuple.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/Tuple.java @@ -16,6 +16,4 @@ package dev.brighten.antivpn.utils; -public record Tuple(F first, S second) { - -} +public record Tuple(F first, S second) {} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/config/Configuration.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/config/Configuration.java index 8bfd3a7..b12b641 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/config/Configuration.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/config/Configuration.java @@ -18,506 +18,438 @@ package dev.brighten.antivpn.utils.config; import java.util.*; -public final class Configuration -{ +public final class Configuration { - private static final char SEPARATOR = '.'; - final Map self; - final Map> comments; - private final Configuration defaults; + private static final char SEPARATOR = '.'; + final Map self; + final Map> comments; + private final Configuration defaults; - public Configuration() - { - this( null ); + public Configuration() { + this(null); + } + + public Configuration(Configuration defaults) { + this(new LinkedHashMap(), defaults); + } + + Configuration(Map map, Configuration defaults) { + this.self = new LinkedHashMap<>(); + this.defaults = defaults; + comments = new HashMap<>(); + + for (Map.Entry entry : map.entrySet()) { + String key = (entry.getKey() == null) ? "null" : entry.getKey().toString(); + + if (entry.getValue() instanceof Map) { + this.self.put( + key, + new Configuration( + (Map) entry.getValue(), (defaults == null) ? null : defaults.getSection(key))); + } else { + this.self.put(key, entry.getValue()); + } } + } - public Configuration(Configuration defaults) - { - this( new LinkedHashMap(), defaults ); - } + public void loadFromString(String contents) { - Configuration(Map map, Configuration defaults) - { - this.self = new LinkedHashMap<>(); - this.defaults = defaults; - comments = new HashMap<>(); + List list = new ArrayList<>(); + Collections.addAll(list, contents.split("\n")); - for ( Map.Entry entry : map.entrySet() ) - { - String key = ( entry.getKey() == null ) ? "null" : entry.getKey().toString(); + int currentLayer = 0; + String currentPath = ""; - if ( entry.getValue() instanceof Map ) - { - this.self.put( key, new Configuration( (Map) entry.getValue(), ( defaults == null ) ? null : defaults.getSection( key ) ) ); - } else - { - this.self.put( key, entry.getValue() ); - } + int lineNumber = 0; + for (Iterator iterator = list.iterator(); iterator.hasNext(); lineNumber++) { + String line = iterator.next(); + + String trimmed = line.trim(); + if (trimmed.startsWith("#") || trimmed.isEmpty()) { + addCommentLine(currentPath, line); + continue; + } + + if (!line.isEmpty()) { + if (line.contains(":")) { + + int layerFromLine = getLayerFromLine(line, lineNumber); + + if (layerFromLine < currentLayer) { + currentPath = regressPathBy(currentLayer - layerFromLine, currentPath); + } + + String key = getKeyFromLine(line); + + if (currentLayer == 0) { + currentPath = key; + } else { + currentPath += "." + key; + } } + } + } + } + + private void addCommentLine(String currentPath, String line) { + + List list = comments.get(currentPath); + if (list == null) { + list = new ArrayList<>(); + } + list.add(line); + + comments.put(currentPath, list); + } + + String getKeyFromLine(String line) { + String key = null; + + for (int i = 0; i < line.length(); i++) { + if (line.charAt(i) == ':') { + key = line.substring(0, i); + break; + } } - public void loadFromString(String contents) { + return key == null ? null : key.trim(); + } - List list = new ArrayList<>(); - Collections.addAll(list, contents.split("\n")); + String regressPathBy(int i, String currentPath) { + if (i <= 0) { + return currentPath; + } + String[] split = currentPath.split("\\."); - int currentLayer = 0; - String currentPath = ""; - - int lineNumber = 0; - for(Iterator iterator = list.iterator(); iterator.hasNext(); lineNumber++) { - String line = iterator.next(); - - String trimmed = line.trim(); - if(trimmed.startsWith("#") || trimmed.isEmpty()) { - addCommentLine(currentPath, line); - continue; - } - - if(!line.isEmpty()) { - if(line.contains(":")) { - - int layerFromLine = getLayerFromLine(line, lineNumber); - - if(layerFromLine < currentLayer) { - currentPath = regressPathBy(currentLayer - layerFromLine, currentPath); - } - - String key = getKeyFromLine(line); - - if(currentLayer == 0) { - currentPath = key; - } - else { - currentPath += "." + key; - } - } - } - } + String rebuild = ""; + for (int j = 0; j < split.length - i; j++) { + rebuild += split[j]; + if (j <= (split.length - j)) { + rebuild += "."; + } } - private void addCommentLine(String currentPath, String line) { + return rebuild; + } - List list = comments.get(currentPath); - if(list == null) { - list = new ArrayList<>(); - } - list.add(line); + int getLayerFromLine(String line, int lineNumber) { - comments.put(currentPath, list); + double d = 0; + for (int i = 0; i < line.length(); i++) { + if (line.charAt(i) == ' ') { + d += 0.5; + } else { + break; + } } - String getKeyFromLine(String line) { - String key = null; + return (int) d; + } - for(int i = 0; i < line.length(); i++) { - if(line.charAt(i) == ':') { - key = line.substring(0, i); - break; - } - } - - return key == null ? null : key.trim(); + private Configuration getSectionFor(String path) { + int index = path.indexOf(SEPARATOR); + if (index == -1) { + return this; } - String regressPathBy(int i, String currentPath) { - if(i <= 0) { - return currentPath; - } - String[] split = currentPath.split("\\."); - - String rebuild = ""; - for(int j = 0; j < split.length - i; j++) { - rebuild += split[j]; - if(j <= (split.length - j)) { - rebuild += "."; - } - } - - return rebuild; + String root = path.substring(0, index); + Object section = self.get(root); + if (section == null) { + section = new Configuration((defaults == null) ? null : defaults.getSection(root)); + self.put(root, section); } - int getLayerFromLine(String line, int lineNumber) { + return (Configuration) section; + } - double d = 0; - for(int i = 0; i < line.length(); i++) { - if(line.charAt(i) == ' ') { - d += 0.5; - } - else { - break; - } - } - - return (int) d; + private String getChild(String path) { + int index = path.indexOf(SEPARATOR); + return (index == -1) ? path : path.substring(index + 1); + } + /*------------------------------------------------------------------------*/ + @SuppressWarnings("unchecked") + public T get(String path, T def) { + Configuration section = getSectionFor(path); + Object val; + if (section == this) { + val = self.get(path); + } else { + val = section.get(getChild(path), def); } - private Configuration getSectionFor(String path) - { - int index = path.indexOf( SEPARATOR ); - if ( index == -1 ) - { - return this; - } - - String root = path.substring( 0, index ); - Object section = self.get( root ); - if ( section == null ) - { - section = new Configuration( ( defaults == null ) ? null : defaults.getSection( root ) ); - self.put( root, section ); - } - - return (Configuration) section; + if (val == null && def instanceof Configuration) { + self.put(path, def); } - private String getChild(String path) - { - int index = path.indexOf( SEPARATOR ); - return ( index == -1 ) ? path : path.substring( index + 1 ); + return (val != null) ? (T) val : def; + } + + public boolean contains(String path) { + return get(path, null) != null; + } + + public Object get(String path) { + return get(path, getDefault(path)); + } + + public Object getDefault(String path) { + return (defaults == null) ? null : defaults.get(path); + } + + public void set(String path, Object value) { + if (value instanceof Map) { + value = new Configuration((Map) value, (defaults == null) ? null : defaults.getSection(path)); } - /*------------------------------------------------------------------------*/ - @SuppressWarnings("unchecked") - public T get(String path, T def) - { - Configuration section = getSectionFor( path ); - Object val; - if ( section == this ) - { - val = self.get( path ); - } else - { - val = section.get( getChild( path ), def ); - } + Configuration section = getSectionFor(path); + if (section == this) { + if (value == null) { + self.remove(path); + } else { + self.put(path, value); + } + } else { + section.set(getChild(path), value); + } + } - if ( val == null && def instanceof Configuration ) - { - self.put( path, def ); - } + /*------------------------------------------------------------------------*/ + public Configuration getSection(String path) { + Object def = getDefault(path); + return (Configuration) + get( + path, + (def instanceof Configuration) + ? def + : new Configuration((defaults == null) ? null : defaults.getSection(path))); + } - return ( val != null ) ? (T) val : def; + /** + * Gets keys, not deep by default. + * + * @return top level keys for this section + */ + public Collection getKeys() { + return new LinkedHashSet<>(self.keySet()); + } + + /*------------------------------------------------------------------------*/ + public byte getByte(String path) { + Object def = getDefault(path); + return getByte(path, (def instanceof Number) ? ((Number) def).byteValue() : 0); + } + + public byte getByte(String path, byte def) { + Object val = get(path, def); + return (val instanceof Number) ? ((Number) val).byteValue() : def; + } + + public List getByteList(String path) { + List list = getList(path); + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Number) { + result.add(((Number) object).byteValue()); + } } - public boolean contains(String path) - { - return get( path, null ) != null; + return result; + } + + public short getShort(String path) { + Object def = getDefault(path); + return getShort(path, (def instanceof Number) ? ((Number) def).shortValue() : 0); + } + + public short getShort(String path, short def) { + Object val = get(path, def); + return (val instanceof Number) ? ((Number) val).shortValue() : def; + } + + public List getShortList(String path) { + List list = getList(path); + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Number) { + result.add(((Number) object).shortValue()); + } } - public Object get(String path) - { - return get( path, getDefault( path ) ); + return result; + } + + public int getInt(String path) { + Object def = getDefault(path); + return getInt(path, (def instanceof Number) ? ((Number) def).intValue() : 0); + } + + public int getInt(String path, int def) { + Object val = get(path, def); + return (val instanceof Number) ? ((Number) val).intValue() : def; + } + + public List getIntList(String path) { + List list = getList(path); + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Number) { + result.add(((Number) object).intValue()); + } } - public Object getDefault(String path) - { - return ( defaults == null ) ? null : defaults.get( path ); + return result; + } + + public long getLong(String path) { + Object def = getDefault(path); + return getLong(path, (def instanceof Number) ? ((Number) def).longValue() : 0); + } + + public long getLong(String path, long def) { + Object val = get(path, def); + return (val instanceof Number) ? ((Number) val).longValue() : def; + } + + public List getLongList(String path) { + List list = getList(path); + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Number) { + result.add(((Number) object).longValue()); + } } - public void set(String path, Object value) - { - if ( value instanceof Map ) - { - value = new Configuration( (Map) value, ( defaults == null ) ? null : defaults.getSection( path ) ); - } + return result; + } - Configuration section = getSectionFor( path ); - if ( section == this ) - { - if ( value == null ) - { - self.remove( path ); - } else - { - self.put( path, value ); - } - } else - { - section.set( getChild( path ), value ); - } + public float getFloat(String path) { + Object def = getDefault(path); + return getFloat(path, (def instanceof Number) ? ((Number) def).floatValue() : 0); + } + + public float getFloat(String path, float def) { + Object val = get(path, def); + return (val instanceof Number) ? ((Number) val).floatValue() : def; + } + + public List getFloatList(String path) { + List list = getList(path); + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Number) { + result.add(((Number) object).floatValue()); + } } - /*------------------------------------------------------------------------*/ - public Configuration getSection(String path) - { - Object def = getDefault( path ); - return (Configuration) get( path, ( def instanceof Configuration ) ? def : new Configuration( ( defaults == null ) ? null : defaults.getSection( path ) ) ); + return result; + } + + public double getDouble(String path) { + Object def = getDefault(path); + return getDouble(path, (def instanceof Number) ? ((Number) def).doubleValue() : 0); + } + + public double getDouble(String path, double def) { + Object val = get(path, def); + return (val instanceof Number) ? ((Number) val).doubleValue() : def; + } + + public List getDoubleList(String path) { + List list = getList(path); + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Number) { + result.add(((Number) object).doubleValue()); + } } - /** - * Gets keys, not deep by default. - * - * @return top level keys for this section - */ - public Collection getKeys() - { - return new LinkedHashSet<>( self.keySet() ); + return result; + } + + public boolean getBoolean(String path) { + Object def = getDefault(path); + return getBoolean(path, (def instanceof Boolean) ? (Boolean) def : false); + } + + public boolean getBoolean(String path, boolean def) { + Object val = get(path, def); + return (val instanceof Boolean) ? (Boolean) val : def; + } + + public List getBooleanList(String path) { + List list = getList(path); + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Boolean) { + result.add((Boolean) object); + } } - /*------------------------------------------------------------------------*/ - public byte getByte(String path) - { - Object def = getDefault( path ); - return getByte( path, ( def instanceof Number ) ? ( (Number) def ).byteValue() : 0 ); + return result; + } + + public char getChar(String path) { + Object def = getDefault(path); + return getChar(path, (def instanceof Character) ? (Character) def : '\u0000'); + } + + public char getChar(String path, char def) { + Object val = get(path, def); + return (val instanceof Character) ? (Character) val : def; + } + + public List getCharList(String path) { + List list = getList(path); + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof Character) { + result.add((Character) object); + } } - public byte getByte(String path, byte def) - { - Object val = get( path, def ); - return ( val instanceof Number ) ? ( (Number) val ).byteValue() : def; + return result; + } + + public String getString(String path) { + Object def = getDefault(path); + return getString(path, (def instanceof String) ? (String) def : ""); + } + + public String getString(String path, String def) { + Object val = get(path, def); + return (val instanceof String) ? (String) val : def; + } + + public List getStringList(String path) { + List list = getList(path); + List result = new ArrayList<>(); + + for (Object object : list) { + if (object instanceof String) { + result.add((String) object); + } } - public List getByteList(String path) - { - List list = getList( path ); - List result = new ArrayList<>(); + return result; + } - for ( Object object : list ) - { - if ( object instanceof Number ) - { - result.add( ( (Number) object ).byteValue() ); - } - } + /*------------------------------------------------------------------------*/ + public List getList(String path) { + Object def = getDefault(path); + return getList(path, (def instanceof List) ? (List) def : Collections.EMPTY_LIST); + } - return result; - } - - public short getShort(String path) - { - Object def = getDefault( path ); - return getShort( path, ( def instanceof Number ) ? ( (Number) def ).shortValue() : 0 ); - } - - public short getShort(String path, short def) - { - Object val = get( path, def ); - return ( val instanceof Number ) ? ( (Number) val ).shortValue() : def; - } - - public List getShortList(String path) - { - List list = getList( path ); - List result = new ArrayList<>(); - - for ( Object object : list ) - { - if ( object instanceof Number ) - { - result.add( ( (Number) object ).shortValue() ); - } - } - - return result; - } - - public int getInt(String path) - { - Object def = getDefault( path ); - return getInt( path, ( def instanceof Number ) ? ( (Number) def ).intValue() : 0 ); - } - - public int getInt(String path, int def) - { - Object val = get( path, def ); - return ( val instanceof Number ) ? ( (Number) val ).intValue() : def; - } - - public List getIntList(String path) - { - List list = getList( path ); - List result = new ArrayList<>(); - - for ( Object object : list ) - { - if ( object instanceof Number ) - { - result.add( ( (Number) object ).intValue() ); - } - } - - return result; - } - - public long getLong(String path) - { - Object def = getDefault( path ); - return getLong( path, ( def instanceof Number ) ? ( (Number) def ).longValue() : 0 ); - } - - public long getLong(String path, long def) - { - Object val = get( path, def ); - return ( val instanceof Number ) ? ( (Number) val ).longValue() : def; - } - - public List getLongList(String path) - { - List list = getList( path ); - List result = new ArrayList<>(); - - for ( Object object : list ) - { - if ( object instanceof Number ) - { - result.add( ( (Number) object ).longValue() ); - } - } - - return result; - } - - public float getFloat(String path) - { - Object def = getDefault( path ); - return getFloat( path, ( def instanceof Number ) ? ( (Number) def ).floatValue() : 0 ); - } - - public float getFloat(String path, float def) - { - Object val = get( path, def ); - return ( val instanceof Number ) ? ( (Number) val ).floatValue() : def; - } - - public List getFloatList(String path) - { - List list = getList( path ); - List result = new ArrayList<>(); - - for ( Object object : list ) - { - if ( object instanceof Number ) - { - result.add( ( (Number) object ).floatValue() ); - } - } - - return result; - } - - public double getDouble(String path) - { - Object def = getDefault( path ); - return getDouble( path, ( def instanceof Number ) ? ( (Number) def ).doubleValue() : 0 ); - } - - public double getDouble(String path, double def) - { - Object val = get( path, def ); - return ( val instanceof Number ) ? ( (Number) val ).doubleValue() : def; - } - - public List getDoubleList(String path) - { - List list = getList( path ); - List result = new ArrayList<>(); - - for ( Object object : list ) - { - if ( object instanceof Number ) - { - result.add( ( (Number) object ).doubleValue() ); - } - } - - return result; - } - - public boolean getBoolean(String path) - { - Object def = getDefault( path ); - return getBoolean( path, ( def instanceof Boolean ) ? (Boolean) def : false ); - } - - public boolean getBoolean(String path, boolean def) - { - Object val = get( path, def ); - return ( val instanceof Boolean ) ? (Boolean) val : def; - } - - public List getBooleanList(String path) - { - List list = getList( path ); - List result = new ArrayList<>(); - - for ( Object object : list ) - { - if ( object instanceof Boolean ) - { - result.add( (Boolean) object ); - } - } - - return result; - } - - public char getChar(String path) - { - Object def = getDefault( path ); - return getChar( path, ( def instanceof Character ) ? (Character) def : '\u0000' ); - } - - public char getChar(String path, char def) - { - Object val = get( path, def ); - return ( val instanceof Character ) ? (Character) val : def; - } - - public List getCharList(String path) - { - List list = getList( path ); - List result = new ArrayList<>(); - - for ( Object object : list ) - { - if ( object instanceof Character ) - { - result.add( (Character) object ); - } - } - - return result; - } - - public String getString(String path) - { - Object def = getDefault( path ); - return getString( path, ( def instanceof String ) ? (String) def : "" ); - } - - public String getString(String path, String def) - { - Object val = get( path, def ); - return ( val instanceof String ) ? (String) val : def; - } - - public List getStringList(String path) - { - List list = getList( path ); - List result = new ArrayList<>(); - - for ( Object object : list ) - { - if ( object instanceof String ) - { - result.add( (String) object ); - } - } - - return result; - } - - /*------------------------------------------------------------------------*/ - public List getList(String path) - { - Object def = getDefault( path ); - return getList( path, ( def instanceof List ) ? (List) def : Collections.EMPTY_LIST ); - } - - public List getList(String path, List def) - { - Object val = get( path, def ); - return ( val instanceof List ) ? (List) val : def; - } -} \ No newline at end of file + public List getList(String path, List def) { + Object val = get(path, def); + return (val instanceof List) ? (List) val : def; + } +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/config/ConfigurationProvider.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/config/ConfigurationProvider.java index 0e12ccd..9bceaab 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/config/ConfigurationProvider.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/config/ConfigurationProvider.java @@ -20,46 +20,42 @@ import java.io.*; import java.util.HashMap; import java.util.Map; -public abstract class ConfigurationProvider -{ +public abstract class ConfigurationProvider { - public static final Map, ConfigurationProvider> providers = new HashMap<>(); + public static final Map, ConfigurationProvider> providers = + new HashMap<>(); - static - { - try - { - providers.put( YamlConfiguration.class, new YamlConfiguration() ); - } catch ( NoClassDefFoundError ex ) - { - ex.printStackTrace(); - // Ignore, no SnakeYAML - } + static { + try { + providers.put(YamlConfiguration.class, new YamlConfiguration()); + } catch (NoClassDefFoundError ex) { + ex.printStackTrace(); + // Ignore, no SnakeYAML } + } - public static ConfigurationProvider getProvider(Class provider) - { - return providers.get( provider ); - } + public static ConfigurationProvider getProvider(Class provider) { + return providers.get(provider); + } - /*------------------------------------------------------------------------*/ - public abstract void save(Configuration config, File file) throws IOException; + /*------------------------------------------------------------------------*/ + public abstract void save(Configuration config, File file) throws IOException; - public abstract void save(Configuration config, Writer writer); + public abstract void save(Configuration config, Writer writer); - public abstract Configuration load(File file) throws IOException; + public abstract Configuration load(File file) throws IOException; - public abstract Configuration load(File file, Configuration defaults) throws IOException; + public abstract Configuration load(File file, Configuration defaults) throws IOException; - public abstract Configuration load(Reader reader); + public abstract Configuration load(Reader reader); - public abstract Configuration load(Reader reader, Configuration defaults); + public abstract Configuration load(Reader reader, Configuration defaults); - public abstract Configuration load(InputStream is); + public abstract Configuration load(InputStream is); - public abstract Configuration load(InputStream is, Configuration defaults); + public abstract Configuration load(InputStream is, Configuration defaults); - public abstract Configuration load(String string); + public abstract Configuration load(String string); - public abstract Configuration load(String string, Configuration defaults); -} \ No newline at end of file + public abstract Configuration load(String string, Configuration defaults); +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/config/YamlConfiguration.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/config/YamlConfiguration.java index 13a88fa..7d990c9 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/config/YamlConfiguration.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/config/YamlConfiguration.java @@ -16,6 +16,10 @@ package dev.brighten.antivpn.utils.config; +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.*; import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.SneakyThrows; @@ -25,173 +29,161 @@ import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.Constructor; import org.yaml.snakeyaml.representer.Representer; -import java.io.*; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.*; - @NoArgsConstructor(access = AccessLevel.PACKAGE) -public class YamlConfiguration extends ConfigurationProvider -{ +public class YamlConfiguration extends ConfigurationProvider { - private final ThreadLocal yaml = new ThreadLocal() - { + private final ThreadLocal yaml = + new ThreadLocal() { @Override - protected Yaml initialValue() - { - DumperOptions options = new DumperOptions(); - options.setDefaultFlowStyle( DumperOptions.FlowStyle.BLOCK ); - Representer representer = new Representer(options) - { + protected Yaml initialValue() { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + Representer representer = + new Representer(options) { { - representers.put( Configuration.class, data -> represent( ( (Configuration) data ).self )); + representers.put( + Configuration.class, data -> represent(((Configuration) data).self)); } - }; + }; - representer.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - return new Yaml( new Constructor(new LoaderOptions()), representer, options ); + representer.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + return new Yaml(new Constructor(new LoaderOptions()), representer, options); } - }; + }; - @Override - public void save(Configuration config, File file) throws IOException - { - try ( Writer writer = new OutputStreamWriter( new FileOutputStream( file ), StandardCharsets.UTF_8 ) ) - { - save( config, writer ); + @Override + public void save(Configuration config, File file) throws IOException { + try (Writer writer = + new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) { + save(config, writer); + } + } + + @Override + public void save(Configuration config, Writer writer) { + String contents = this.yaml.get().dump(config.self); + if (contents.equals("{}\n")) { + contents = ""; + } + + List list = new ArrayList<>(); + Collections.addAll(list, contents.split("\n")); + + int currentLayer = 0; + StringBuilder currentPath = new StringBuilder(); + + StringBuilder sb = new StringBuilder(); + + int lineNumber = 0; + for (Iterator iterator = list.iterator(); iterator.hasNext(); lineNumber++) { + String line = iterator.next(); + sb.append(line); + sb.append('\n'); + + if (!line.isEmpty()) { + if (line.contains(":")) { + + int layerFromLine = config.getLayerFromLine(line, lineNumber); + + if (layerFromLine < currentLayer) { + currentPath = + new StringBuilder( + config.regressPathBy(currentLayer - layerFromLine, currentPath.toString())); + } + + String key = config.getKeyFromLine(line); + + if (currentLayer == 0) { + currentPath = new StringBuilder(key); + } else { + currentPath.append("." + key); + } + + String path = currentPath.toString(); + if (config.comments.containsKey(path)) { + config + .comments + .get(path) + .forEach( + string -> { + sb.append(string); + sb.append('\n'); + }); + } } + } } - @Override - public void save(Configuration config, Writer writer) - { - String contents = this.yaml.get().dump(config.self); - if (contents.equals("{}\n")) { - contents = ""; - } - - List list = new ArrayList<>(); - Collections.addAll(list, contents.split("\n")); - - int currentLayer = 0; - StringBuilder currentPath = new StringBuilder(); - - StringBuilder sb = new StringBuilder(); - - int lineNumber = 0; - for(Iterator iterator = list.iterator(); iterator.hasNext(); lineNumber++) { - String line = iterator.next(); - sb.append(line); - sb.append('\n'); - - if (!line.isEmpty()) { - if (line.contains(":")) { - - int layerFromLine = config.getLayerFromLine(line, lineNumber); - - if (layerFromLine < currentLayer) { - currentPath = new StringBuilder(config.regressPathBy(currentLayer - layerFromLine, currentPath.toString())); - } - - String key = config.getKeyFromLine(line); - - if (currentLayer == 0) { - currentPath = new StringBuilder(key); - } else { - currentPath.append("." + key); - } - - String path = currentPath.toString(); - if (config.comments.containsKey(path)) { - config.comments.get(path).forEach(string -> { - sb.append(string); - sb.append('\n'); - }); - } - } - } - } - - try { - writer.write(sb.toString()); - } catch (IOException e) { - e.printStackTrace(); - } + try { + writer.write(sb.toString()); + } catch (IOException e) { + e.printStackTrace(); } - @Override - public Configuration load(File file) throws IOException - { - return load( file, null ); + } + + @Override + public Configuration load(File file) throws IOException { + return load(file, null); + } + + @Override + public Configuration load(File file, Configuration defaults) throws IOException { + try (FileInputStream is = new FileInputStream(file)) { + return load(is, defaults); + } + } + + @Override + public Configuration load(Reader reader) { + return load(reader, null); + } + + @SneakyThrows + @Override + public Configuration load(Reader reader, Configuration defaults) { + BufferedReader input = + reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); + StringBuilder builder = new StringBuilder(); + + String line; + try { + while ((line = input.readLine()) != null) { + builder.append(line); + builder.append('\n'); + } + } finally { + input.close(); } - @Override - public Configuration load(File file, Configuration defaults) throws IOException - { - try ( FileInputStream is = new FileInputStream( file ) ) - { - return load( is, defaults ); - } - } + return load(builder.toString(), defaults); + } - @Override - public Configuration load(Reader reader) - { - return load( reader, null ); - } + @Override + public Configuration load(InputStream is) { + return this.load(new InputStreamReader(is, Charset.defaultCharset())); + } - @SneakyThrows - @Override - public Configuration load(Reader reader, Configuration defaults) - { - BufferedReader input = reader instanceof BufferedReader ? (BufferedReader)reader : new BufferedReader(reader); - StringBuilder builder = new StringBuilder(); + @Override + public Configuration load(InputStream is, Configuration defaults) { + return this.load(new InputStreamReader(is, Charset.defaultCharset()), defaults); + } - String line; - try { - while((line = input.readLine()) != null) { - builder.append(line); - builder.append('\n'); - } - } finally { - input.close(); - } + @Override + public Configuration load(String string) { + return load(string, null); + } + @Override + @SuppressWarnings("unchecked") + public Configuration load(String contents, Configuration defaults) { + Map map; + LoaderOptions loaderOptions = new LoaderOptions(); + loaderOptions.setMaxAliasesForCollections(2147483647); + map = this.yaml.get().loadAs(contents, LinkedHashMap.class); - return load(builder.toString(), defaults); - } + Configuration config = new Configuration(map, defaults); + config.loadFromString(contents); - @Override - public Configuration load(InputStream is) - { - return this.load(new InputStreamReader(is, Charset.defaultCharset())); - } - - @Override - public Configuration load(InputStream is, Configuration defaults) - { - return this.load(new InputStreamReader(is, Charset.defaultCharset()), defaults); - } - - @Override - public Configuration load(String string) - { - return load( string, null ); - } - - - @Override - @SuppressWarnings("unchecked") - public Configuration load(String contents, Configuration defaults) - { - Map map; - LoaderOptions loaderOptions = new LoaderOptions(); - loaderOptions.setMaxAliasesForCollections(2147483647); - map = this.yaml.get().loadAs(contents, LinkedHashMap.class); - - Configuration config = new Configuration( map, defaults ); - config.loadFromString(contents); - - return config; - } - -} \ No newline at end of file + return config; + } +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/CDL.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/CDL.java index 13da975..0c7e0ba 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/CDL.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/CDL.java @@ -17,266 +17,254 @@ package dev.brighten.antivpn.utils.json; /** - * This provides static methods to convert comma delimited text into a - * JSONArray, and to covert a JSONArray into comma delimited text. Comma - * delimited text is a very popular format for data interchange. It is - * understood by most database, spreadsheet, and organizer programs. - *

- * Each row of text represents a row in a table or a data record. Each row - * ends with a NEWLINE character. Each row contains one or more values. - * Values are separated by commas. A value can contain any character except - * for comma, unless is is wrapped in single quotes or double quotes. - *

- * The first row usually contains the names of the columns. - *

- * A comma delimited list can be converted into a JSONArray of JSONObjects. - * The names for the elements in the JSONObjects can be taken from the names - * in the first row. + * This provides static methods to convert comma delimited text into a JSONArray, and to covert a + * JSONArray into comma delimited text. Comma delimited text is a very popular format for data + * interchange. It is understood by most database, spreadsheet, and organizer programs. + * + *

Each row of text represents a row in a table or a data record. Each row ends with a NEWLINE + * character. Each row contains one or more values. Values are separated by commas. A value can + * contain any character except for comma, unless is is wrapped in single quotes or double quotes. + * + *

The first row usually contains the names of the columns. + * + *

A comma delimited list can be converted into a JSONArray of JSONObjects. The names for the + * elements in the JSONObjects can be taken from the names in the first row. * * @author JSON.org * @version 2010-12-24 */ public class CDL { - /** - * Get the next value. The value can be wrapped in quotes. The value can - * be empty. - * - * @param x A JSONTokener of the source text. - * @return The value string, or null if empty. - * @throws JSONException if the quoted string is badly formed. - */ - private static String getValue(JSONTokener x) throws JSONException { - char c; - char q; - StringBuffer sb; - do { - c = x.next(); - } while (c == ' ' || c == '\t'); - switch (c) { - case 0: - return null; - case '"': - case '\'': - q = c; - sb = new StringBuffer(); - for (; ; ) { - c = x.next(); - if (c == q) { - break; - } - if (c == 0 || c == '\n' || c == '\r') { - throw x.syntaxError("Missing close quote '" + q + "'."); - } - sb.append(c); - } - return sb.toString(); - case ',': - x.back(); - return ""; - default: - x.back(); - return x.nextTo(','); - } - } - - /** - * Produce a JSONArray of strings from a row of comma delimited values. - * - * @param x A JSONTokener of the source text. - * @return A JSONArray of strings. - * @throws JSONException - */ - public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException { - JSONArray ja = new JSONArray(); - for (; ; ) { - String value = getValue(x); - char c = x.next(); - if (value == null || - (ja.length() == 0 && value.length() == 0 && c != ',')) { - return null; - } - ja.put(value); - for (; ; ) { - if (c == ',') { - break; - } - if (c != ' ') { - if (c == '\n' || c == '\r' || c == 0) { - return ja; - } - throw x.syntaxError("Bad character '" + c + "' (" + - (int) c + ")."); - } - c = x.next(); - } - } - } - - /** - * Produce a JSONObject from a row of comma delimited text, using a - * parallel JSONArray of strings to provides the names of the elements. - * - * @param names A JSONArray of names. This is commonly obtained from the - * first row of a comma delimited text file using the rowToJSONArray - * method. - * @param x A JSONTokener of the source text. - * @return A JSONObject combining the names and values. - * @throws JSONException - */ - public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) - throws JSONException { - JSONArray ja = rowToJSONArray(x); - return ja != null ? ja.toJSONObject(names) : null; - } - - /** - * Produce a comma delimited text row from a JSONArray. Values containing - * the comma character will be quoted. Troublesome characters may be - * removed. - * - * @param ja A JSONArray of strings. - * @return A string ending in NEWLINE. - */ - public static String rowToString(JSONArray ja) { - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < ja.length(); i += 1) { - if (i > 0) { - sb.append(','); - } - Object object = ja.opt(i); - if (object != null) { - String string = object.toString(); - if (string.length() > 0 && (string.indexOf(',') >= 0 || - string.indexOf('\n') >= 0 || string.indexOf('\r') >= 0 || - string.indexOf(0) >= 0 || string.charAt(0) == '"')) { - sb.append('"'); - int length = string.length(); - for (int j = 0; j < length; j += 1) { - char c = string.charAt(j); - if (c >= ' ' && c != '"') { - sb.append(c); - } - } - sb.append('"'); - } else { - sb.append(string); - } - } - } - sb.append('\n'); - return sb.toString(); - } - - /** - * Produce a JSONArray of JSONObjects from a comma delimited text string, - * using the first row as a source of names. - * - * @param string The comma delimited text. - * @return A JSONArray of JSONObjects. - * @throws JSONException - */ - public static JSONArray toJSONArray(String string) throws JSONException { - return toJSONArray(new JSONTokener(string)); - } - - /** - * Produce a JSONArray of JSONObjects from a comma delimited text string, - * using the first row as a source of names. - * - * @param x The JSONTokener containing the comma delimited text. - * @return A JSONArray of JSONObjects. - * @throws JSONException - */ - public static JSONArray toJSONArray(JSONTokener x) throws JSONException { - return toJSONArray(rowToJSONArray(x), x); - } - - /** - * Produce a JSONArray of JSONObjects from a comma delimited text string - * using a supplied JSONArray as the source of element names. - * - * @param names A JSONArray of strings. - * @param string The comma delimited text. - * @return A JSONArray of JSONObjects. - * @throws JSONException - */ - public static JSONArray toJSONArray(JSONArray names, String string) - throws JSONException { - return toJSONArray(names, new JSONTokener(string)); - } - - /** - * Produce a JSONArray of JSONObjects from a comma delimited text string - * using a supplied JSONArray as the source of element names. - * - * @param names A JSONArray of strings. - * @param x A JSONTokener of the source text. - * @return A JSONArray of JSONObjects. - * @throws JSONException - */ - public static JSONArray toJSONArray(JSONArray names, JSONTokener x) - throws JSONException { - if (names == null || names.length() == 0) { - return null; - } - JSONArray ja = new JSONArray(); - for (; ; ) { - JSONObject jo = rowToJSONObject(names, x); - if (jo == null) { - break; - } - ja.put(jo); - } - if (ja.length() == 0) { - return null; - } - return ja; - } - - - /** - * Produce a comma delimited text from a JSONArray of JSONObjects. The - * first row will be a list of names obtained by inspecting the first - * JSONObject. - * - * @param ja A JSONArray of JSONObjects. - * @return A comma delimited text. - * @throws JSONException - */ - public static String toString(JSONArray ja) throws JSONException { - JSONObject jo = ja.optJSONObject(0); - if (jo != null) { - JSONArray names = jo.names(); - if (names != null) { - return rowToString(names) + toString(names, ja); - } - } + /** + * Get the next value. The value can be wrapped in quotes. The value can be empty. + * + * @param x A JSONTokener of the source text. + * @return The value string, or null if empty. + * @throws JSONException if the quoted string is badly formed. + */ + private static String getValue(JSONTokener x) throws JSONException { + char c; + char q; + StringBuffer sb; + do { + c = x.next(); + } while (c == ' ' || c == '\t'); + switch (c) { + case 0: return null; - } - - /** - * Produce a comma delimited text from a JSONArray of JSONObjects using - * a provided list of names. The list of names is not included in the - * output. - * - * @param names A JSONArray of strings. - * @param ja A JSONArray of JSONObjects. - * @return A comma delimited text. - * @throws JSONException - */ - public static String toString(JSONArray names, JSONArray ja) - throws JSONException { - if (names == null || names.length() == 0) { - return null; - } - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < ja.length(); i += 1) { - JSONObject jo = ja.optJSONObject(i); - if (jo != null) { - sb.append(rowToString(jo.toJSONArray(names))); - } + case '"': + case '\'': + q = c; + sb = new StringBuffer(); + for (; ; ) { + c = x.next(); + if (c == q) { + break; + } + if (c == 0 || c == '\n' || c == '\r') { + throw x.syntaxError("Missing close quote '" + q + "'."); + } + sb.append(c); } return sb.toString(); + case ',': + x.back(); + return ""; + default: + x.back(); + return x.nextTo(','); } + } + + /** + * Produce a JSONArray of strings from a row of comma delimited values. + * + * @param x A JSONTokener of the source text. + * @return A JSONArray of strings. + * @throws JSONException + */ + public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException { + JSONArray ja = new JSONArray(); + for (; ; ) { + String value = getValue(x); + char c = x.next(); + if (value == null || (ja.length() == 0 && value.isEmpty() && c != ',')) { + return null; + } + ja.put(value); + for (; ; ) { + if (c == ',') { + break; + } + if (c != ' ') { + if (c == '\n' || c == '\r' || c == 0) { + return ja; + } + throw x.syntaxError("Bad character '" + c + "' (" + (int) c + ")."); + } + c = x.next(); + } + } + } + + /** + * Produce a JSONObject from a row of comma delimited text, using a parallel JSONArray of strings + * to provides the names of the elements. + * + * @param names A JSONArray of names. This is commonly obtained from the first row of a comma + * delimited text file using the rowToJSONArray method. + * @param x A JSONTokener of the source text. + * @return A JSONObject combining the names and values. + * @throws JSONException + */ + public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) throws JSONException { + JSONArray ja = rowToJSONArray(x); + return ja != null ? ja.toJSONObject(names) : null; + } + + /** + * Produce a comma delimited text row from a JSONArray. Values containing the comma character will + * be quoted. Troublesome characters may be removed. + * + * @param ja A JSONArray of strings. + * @return A string ending in NEWLINE. + */ + public static String rowToString(JSONArray ja) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < ja.length(); i += 1) { + if (i > 0) { + sb.append(','); + } + Object object = ja.opt(i); + if (object != null) { + String string = object.toString(); + if (string.length() > 0 + && (string.indexOf(',') >= 0 + || string.indexOf('\n') >= 0 + || string.indexOf('\r') >= 0 + || string.indexOf(0) >= 0 + || string.charAt(0) == '"')) { + sb.append('"'); + int length = string.length(); + for (int j = 0; j < length; j += 1) { + char c = string.charAt(j); + if (c >= ' ' && c != '"') { + sb.append(c); + } + } + sb.append('"'); + } else { + sb.append(string); + } + } + } + sb.append('\n'); + return sb.toString(); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string, using the first row as a + * source of names. + * + * @param string The comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(String string) throws JSONException { + return toJSONArray(new JSONTokener(string)); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string, using the first row as a + * source of names. + * + * @param x The JSONTokener containing the comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONTokener x) throws JSONException { + return toJSONArray(rowToJSONArray(x), x); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string using a supplied + * JSONArray as the source of element names. + * + * @param names A JSONArray of strings. + * @param string The comma delimited text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONArray names, String string) throws JSONException { + return toJSONArray(names, new JSONTokener(string)); + } + + /** + * Produce a JSONArray of JSONObjects from a comma delimited text string using a supplied + * JSONArray as the source of element names. + * + * @param names A JSONArray of strings. + * @param x A JSONTokener of the source text. + * @return A JSONArray of JSONObjects. + * @throws JSONException + */ + public static JSONArray toJSONArray(JSONArray names, JSONTokener x) throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + JSONArray ja = new JSONArray(); + for (; ; ) { + JSONObject jo = rowToJSONObject(names, x); + if (jo == null) { + break; + } + ja.put(jo); + } + if (ja.length() == 0) { + return null; + } + return ja; + } + + /** + * Produce a comma delimited text from a JSONArray of JSONObjects. The first row will be a list of + * names obtained by inspecting the first JSONObject. + * + * @param ja A JSONArray of JSONObjects. + * @return A comma delimited text. + * @throws JSONException + */ + public static String toString(JSONArray ja) throws JSONException { + JSONObject jo = ja.optJSONObject(0); + if (jo != null) { + JSONArray names = jo.names(); + if (names != null) { + return rowToString(names) + toString(names, ja); + } + } + return null; + } + + /** + * Produce a comma delimited text from a JSONArray of JSONObjects using a provided list of names. + * The list of names is not included in the output. + * + * @param names A JSONArray of strings. + * @param ja A JSONArray of JSONObjects. + * @return A comma delimited text. + * @throws JSONException + */ + public static String toString(JSONArray names, JSONArray ja) throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < ja.length(); i += 1) { + JSONObject jo = ja.optJSONObject(i); + if (jo != null) { + sb.append(rowToString(jo.toJSONArray(names))); + } + } + return sb.toString(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/Cookie.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/Cookie.java index 8525a26..39b2ef2 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/Cookie.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/Cookie.java @@ -17,150 +17,139 @@ package dev.brighten.antivpn.utils.json; /** - * Convert a web browser cookie specification to a JSONObject and back. - * JSON and Cookies are both notations for name/value pairs. + * Convert a web browser cookie specification to a JSONObject and back. JSON and Cookies are both + * notations for name/value pairs. * * @author JSON.org * @version 2010-12-24 */ public class Cookie { - /** - * Produce a copy of a string in which the characters '+', '%', '=', ';' - * and control characters are replaced with "%hh". This is a gentle form - * of URL encoding, attempting to cause as little distortion to the - * string as possible. The characters '=' and ';' are meta characters in - * cookies. By convention, they are escaped using the URL-encoding. This is - * only a convention, not a standard. Often, cookies are expected to have - * encoded values. We encode '=' and ';' because we must. We encode '%' and - * '+' because they are meta characters in URL encoding. - * - * @param string The source string. - * @return The escaped result. - */ - public static String escape(String string) { - char c; - String s = string.trim(); - StringBuffer sb = new StringBuffer(); - int length = s.length(); - for (int i = 0; i < length; i += 1) { - c = s.charAt(i); - if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') { - sb.append('%'); - sb.append(Character.forDigit((char) ((c >>> 4) & 0x0f), 16)); - sb.append(Character.forDigit((char) (c & 0x0f), 16)); - } else { - sb.append(c); - } - } - return sb.toString(); + /** + * Produce a copy of a string in which the characters '+', '%', '=', ';' and control characters + * are replaced with "%hh". This is a gentle form of URL encoding, attempting to cause as little + * distortion to the string as possible. The characters '=' and ';' are meta characters in + * cookies. By convention, they are escaped using the URL-encoding. This is only a convention, not + * a standard. Often, cookies are expected to have encoded values. We encode '=' and ';' because + * we must. We encode '%' and '+' because they are meta characters in URL encoding. + * + * @param string The source string. + * @return The escaped result. + */ + public static String escape(String string) { + char c; + String s = string.trim(); + StringBuffer sb = new StringBuffer(); + int length = s.length(); + for (int i = 0; i < length; i += 1) { + c = s.charAt(i); + if (c < ' ' || c == '+' || c == '%' || c == '=' || c == ';') { + sb.append('%'); + sb.append(Character.forDigit((char) ((c >>> 4) & 0x0f), 16)); + sb.append(Character.forDigit((char) (c & 0x0f), 16)); + } else { + sb.append(c); + } } + return sb.toString(); + } - - /** - * Convert a cookie specification string into a JSONObject. The string - * will contain a name value pair separated by '='. The name and the value - * will be unescaped, possibly converting '+' and '%' sequences. The - * cookie properties may follow, separated by ';', also represented as - * name=value (except the secure property, which does not have a value). - * The name will be stored under the key "name", and the value will be - * stored under the key "value". This method does not do checking or - * validation of the parameters. It only converts the cookie string into - * a JSONObject. - * - * @param string The cookie specification string. - * @return A JSONObject containing "name", "value", and possibly other - * members. - * @throws JSONException - */ - public static JSONObject toJSONObject(String string) throws JSONException { - String name; - JSONObject jo = new JSONObject(); - Object value; - JSONTokener x = new JSONTokener(string); - jo.put("name", x.nextTo('=')); - x.next('='); - jo.put("value", x.nextTo(';')); + /** + * Convert a cookie specification string into a JSONObject. The string will contain a name value + * pair separated by '='. The name and the value will be unescaped, possibly converting '+' and + * '%' sequences. The cookie properties may follow, separated by ';', also represented as + * name=value (except the secure property, which does not have a value). The name will be stored + * under the key "name", and the value will be stored under the key "value". This method does not + * do checking or validation of the parameters. It only converts the cookie string into a + * JSONObject. + * + * @param string The cookie specification string. + * @return A JSONObject containing "name", "value", and possibly other members. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + String name; + JSONObject jo = new JSONObject(); + Object value; + JSONTokener x = new JSONTokener(string); + jo.put("name", x.nextTo('=')); + x.next('='); + jo.put("value", x.nextTo(';')); + x.next(); + while (x.more()) { + name = unescape(x.nextTo("=;")); + if (x.next() != '=') { + if (name.equals("secure")) { + value = Boolean.TRUE; + } else { + throw x.syntaxError("Missing '=' in cookie parameter."); + } + } else { + value = unescape(x.nextTo(';')); x.next(); - while (x.more()) { - name = unescape(x.nextTo("=;")); - if (x.next() != '=') { - if (name.equals("secure")) { - value = Boolean.TRUE; - } else { - throw x.syntaxError("Missing '=' in cookie parameter."); - } - } else { - value = unescape(x.nextTo(';')); - x.next(); - } - jo.put(name, value); - } - return jo; + } + jo.put(name, value); } + return jo; + } + /** + * Convert a JSONObject into a cookie specification string. The JSONObject must contain "name" and + * "value" members. If the JSONObject contains "expires", "domain", "path", or "secure" members, + * they will be appended to the cookie specification string. All other members are ignored. + * + * @param jo A JSONObject + * @return A cookie specification string + * @throws JSONException + */ + public static String toString(JSONObject jo) throws JSONException { + StringBuffer sb = new StringBuffer(); - /** - * Convert a JSONObject into a cookie specification string. The JSONObject - * must contain "name" and "value" members. - * If the JSONObject contains "expires", "domain", "path", or "secure" - * members, they will be appended to the cookie specification string. - * All other members are ignored. - * - * @param jo A JSONObject - * @return A cookie specification string - * @throws JSONException - */ - public static String toString(JSONObject jo) throws JSONException { - StringBuffer sb = new StringBuffer(); - - sb.append(escape(jo.getString("name"))); - sb.append("="); - sb.append(escape(jo.getString("value"))); - if (jo.has("expires")) { - sb.append(";expires="); - sb.append(jo.getString("expires")); - } - if (jo.has("domain")) { - sb.append(";domain="); - sb.append(escape(jo.getString("domain"))); - } - if (jo.has("path")) { - sb.append(";path="); - sb.append(escape(jo.getString("path"))); - } - if (jo.optBoolean("secure")) { - sb.append(";secure"); - } - return sb.toString(); + sb.append(escape(jo.getString("name"))); + sb.append("="); + sb.append(escape(jo.getString("value"))); + if (jo.has("expires")) { + sb.append(";expires="); + sb.append(jo.getString("expires")); } - - /** - * Convert %hh sequences to single characters, and - * convert plus to space. - * - * @param string A string that may contain - * + (plus) and - * %hh sequences. - * @return The unescaped string. - */ - public static String unescape(String string) { - int length = string.length(); - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < length; ++i) { - char c = string.charAt(i); - if (c == '+') { - c = ' '; - } else if (c == '%' && i + 2 < length) { - int d = JSONTokener.dehexchar(string.charAt(i + 1)); - int e = JSONTokener.dehexchar(string.charAt(i + 2)); - if (d >= 0 && e >= 0) { - c = (char) (d * 16 + e); - i += 2; - } - } - sb.append(c); - } - return sb.toString(); + if (jo.has("domain")) { + sb.append(";domain="); + sb.append(escape(jo.getString("domain"))); } + if (jo.has("path")) { + sb.append(";path="); + sb.append(escape(jo.getString("path"))); + } + if (jo.optBoolean("secure")) { + sb.append(";secure"); + } + return sb.toString(); + } + + /** + * Convert %hh sequences to single characters, and convert plus to space. + * + * @param string A string that may contain + (plus) and % + * hh sequences. + * @return The unescaped string. + */ + public static String unescape(String string) { + int length = string.length(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < length; ++i) { + char c = string.charAt(i); + if (c == '+') { + c = ' '; + } else if (c == '%' && i + 2 < length) { + int d = JSONTokener.dehexchar(string.charAt(i + 1)); + int e = JSONTokener.dehexchar(string.charAt(i + 2)); + if (d >= 0 && e >= 0) { + c = (char) (d * 16 + e); + i += 2; + } + } + sb.append(c); + } + return sb.toString(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/CookieList.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/CookieList.java index 9a2ba97..683ae51 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/CookieList.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/CookieList.java @@ -26,60 +26,56 @@ import java.util.Iterator; */ public class CookieList { - /** - * Convert a cookie list into a JSONObject. A cookie list is a sequence - * of name/value pairs. The names are separated from the values by '='. - * The pairs are separated by ';'. The names and the values - * will be unescaped, possibly converting '+' and '%' sequences. - *

- * To add a cookie to a cooklist, - * cookielistJSONObject.put(cookieJSONObject.getString("name"), - * cookieJSONObject.getString("value")); - * - * @param string A cookie list string - * @return A JSONObject - * @throws JSONException - */ - public static JSONObject toJSONObject(String string) throws JSONException { - JSONObject jo = new JSONObject(); - JSONTokener x = new JSONTokener(string); - while (x.more()) { - String name = Cookie.unescape(x.nextTo('=')); - x.next('='); - jo.put(name, Cookie.unescape(x.nextTo(';'))); - x.next(); - } - return jo; + /** + * Convert a cookie list into a JSONObject. A cookie list is a sequence of name/value pairs. The + * names are separated from the values by '='. The pairs are separated by ';'. The names and the + * values will be unescaped, possibly converting '+' and '%' sequences. + * + *

To add a cookie to a cooklist, cookielistJSONObject.put(cookieJSONObject.getString("name"), + * cookieJSONObject.getString("value")); + * + * @param string A cookie list string + * @return A JSONObject + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + JSONObject jo = new JSONObject(); + JSONTokener x = new JSONTokener(string); + while (x.more()) { + String name = Cookie.unescape(x.nextTo('=')); + x.next('='); + jo.put(name, Cookie.unescape(x.nextTo(';'))); + x.next(); } + return jo; + } - - /** - * Convert a JSONObject into a cookie list. A cookie list is a sequence - * of name/value pairs. The names are separated from the values by '='. - * The pairs are separated by ';'. The characters '%', '+', '=', and ';' - * in the names and values are replaced by "%hh". - * - * @param jo A JSONObject - * @return A cookie list string - * @throws JSONException - */ - public static String toString(JSONObject jo) throws JSONException { - boolean b = false; - Iterator keys = jo.keys(); - String string; - StringBuffer sb = new StringBuffer(); - while (keys.hasNext()) { - string = keys.next().toString(); - if (!jo.isNull(string)) { - if (b) { - sb.append(';'); - } - sb.append(Cookie.escape(string)); - sb.append("="); - sb.append(Cookie.escape(jo.getString(string))); - b = true; - } + /** + * Convert a JSONObject into a cookie list. A cookie list is a sequence of name/value pairs. The + * names are separated from the values by '='. The pairs are separated by ';'. The characters '%', + * '+', '=', and ';' in the names and values are replaced by "%hh". + * + * @param jo A JSONObject + * @return A cookie list string + * @throws JSONException + */ + public static String toString(JSONObject jo) throws JSONException { + boolean b = false; + Iterator keys = jo.keys(); + String string; + StringBuffer sb = new StringBuffer(); + while (keys.hasNext()) { + string = keys.next().toString(); + if (!jo.isNull(string)) { + if (b) { + sb.append(';'); } - return sb.toString(); + sb.append(Cookie.escape(string)); + sb.append("="); + sb.append(Cookie.escape(jo.getString(string))); + b = true; + } } + return sb.toString(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/HTTP.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/HTTP.java index 12f596d..366def1 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/HTTP.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/HTTP.java @@ -26,135 +26,146 @@ import java.util.Iterator; */ public class HTTP { - /** - * Carriage return/line feed. - */ - public static final String CRLF = "\r\n"; + /** Carriage return/line feed. */ + public static final String CRLF = "\r\n"; - /** - * Convert an HTTP header string into a JSONObject. It can be a request - * header or a response header. A request header will contain - *

{
-     *    Method: "POST" (for example),
-     *    "Request-URI": "/" (for example),
-     *    "HTTP-Version": "HTTP/1.1" (for example)
-     * }
- * A response header will contain - *
{
-     *    "HTTP-Version": "HTTP/1.1" (for example),
-     *    "Fixes-Code": "200" (for example),
-     *    "Reason-Phrase": "OK" (for example)
-     * }
- * In addition, the other parameters in the header will be captured, using - * the HTTP field names as JSON names, so that
-     *    Date: Sun, 26 May 2002 18:06:04 GMT
-     *    Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s
-     *    Cache-Control: no-cache
- * become - *
{...
-     *    Date: "Sun, 26 May 2002 18:06:04 GMT",
-     *    Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s",
-     *    "Cache-Control": "no-cache",
-     * ...}
- * It does no further checking or conversion. It does not parse dates. - * It does not do '%' transforms on URLs. - * - * @param string An HTTP header string. - * @return A JSONObject containing the elements and attributes - * of the XML string. - * @throws JSONException - */ - public static JSONObject toJSONObject(String string) throws JSONException { - JSONObject jo = new JSONObject(); - HTTPTokener x = new HTTPTokener(string); - String token; + /** + * Convert an HTTP header string into a JSONObject. It can be a request header or a response + * header. A request header will contain + * + *
{
+   *    Method: "POST" (for example),
+   *    "Request-URI": "/" (for example),
+   *    "HTTP-Version": "HTTP/1.1" (for example)
+   * }
+ * + * A response header will contain + * + *
{
+   *    "HTTP-Version": "HTTP/1.1" (for example),
+   *    "Fixes-Code": "200" (for example),
+   *    "Reason-Phrase": "OK" (for example)
+   * }
+ * + * In addition, the other parameters in the header will be captured, using the HTTP field names as + * JSON names, so that + * + *
+   *    Date: Sun, 26 May 2002 18:06:04 GMT
+   *    Cookie: Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s
+   *    Cache-Control: no-cache
+ * + * become + * + *
{...
+   *    Date: "Sun, 26 May 2002 18:06:04 GMT",
+   *    Cookie: "Q=q2=PPEAsg--; B=677gi6ouf29bn&b=2&f=s",
+   *    "Cache-Control": "no-cache",
+   * ...}
+ * + * It does no further checking or conversion. It does not parse dates. It does not do '%' + * transforms on URLs. + * + * @param string An HTTP header string. + * @return A JSONObject containing the elements and attributes of the XML string. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + JSONObject jo = new JSONObject(); + HTTPTokener x = new HTTPTokener(string); + String token; - token = x.nextToken(); - if (token.toUpperCase().startsWith("HTTP")) { + token = x.nextToken(); + if (token.toUpperCase().startsWith("HTTP")) { -// Response + // Response - jo.put("HTTP-Version", token); - jo.put("Fixes-Code", x.nextToken()); - jo.put("Reason-Phrase", x.nextTo('\0')); - x.next(); + jo.put("HTTP-Version", token); + jo.put("Fixes-Code", x.nextToken()); + jo.put("Reason-Phrase", x.nextTo('\0')); + x.next(); - } else { + } else { -// Request + // Request - jo.put("Method", token); - jo.put("Request-URI", x.nextToken()); - jo.put("HTTP-Version", x.nextToken()); - } - -// Fields - - while (x.more()) { - String name = x.nextTo(':'); - x.next(':'); - jo.put(name, x.nextTo('\0')); - x.next(); - } - return jo; + jo.put("Method", token); + jo.put("Request-URI", x.nextToken()); + jo.put("HTTP-Version", x.nextToken()); } + // Fields - /** - * Convert a JSONObject into an HTTP header. A request header must contain - *
{
-     *    Method: "POST" (for example),
-     *    "Request-URI": "/" (for example),
-     *    "HTTP-Version": "HTTP/1.1" (for example)
-     * }
- * A response header must contain - *
{
-     *    "HTTP-Version": "HTTP/1.1" (for example),
-     *    "Fixes-Code": "200" (for example),
-     *    "Reason-Phrase": "OK" (for example)
-     * }
- * Any other members of the JSONObject will be output as HTTP fields. - * The result will end with two CRLF pairs. - * - * @param jo A JSONObject - * @return An HTTP header string. - * @throws JSONException if the object does not contain enough - * information. - */ - public static String toString(JSONObject jo) throws JSONException { - Iterator keys = jo.keys(); - String string; - StringBuffer sb = new StringBuffer(); - if (jo.has("Fixes-Code") && jo.has("Reason-Phrase")) { - sb.append(jo.getString("HTTP-Version")); - sb.append(' '); - sb.append(jo.getString("Fixes-Code")); - sb.append(' '); - sb.append(jo.getString("Reason-Phrase")); - } else if (jo.has("Method") && jo.has("Request-URI")) { - sb.append(jo.getString("Method")); - sb.append(' '); - sb.append('"'); - sb.append(jo.getString("Request-URI")); - sb.append('"'); - sb.append(' '); - sb.append(jo.getString("HTTP-Version")); - } else { - throw new JSONException("Not enough material for an HTTP header."); - } - sb.append(CRLF); - while (keys.hasNext()) { - string = keys.next().toString(); - if (!string.equals("HTTP-Version") && !string.equals("Fixes-Code") && - !string.equals("Reason-Phrase") && !string.equals("Method") && - !string.equals("Request-URI") && !jo.isNull(string)) { - sb.append(string); - sb.append(": "); - sb.append(jo.getString(string)); - sb.append(CRLF); - } - } - sb.append(CRLF); - return sb.toString(); + while (x.more()) { + String name = x.nextTo(':'); + x.next(':'); + jo.put(name, x.nextTo('\0')); + x.next(); } + return jo; + } + + /** + * Convert a JSONObject into an HTTP header. A request header must contain + * + *
{
+   *    Method: "POST" (for example),
+   *    "Request-URI": "/" (for example),
+   *    "HTTP-Version": "HTTP/1.1" (for example)
+   * }
+ * + * A response header must contain + * + *
{
+   *    "HTTP-Version": "HTTP/1.1" (for example),
+   *    "Fixes-Code": "200" (for example),
+   *    "Reason-Phrase": "OK" (for example)
+   * }
+ * + * Any other members of the JSONObject will be output as HTTP fields. The result will end with two + * CRLF pairs. + * + * @param jo A JSONObject + * @return An HTTP header string. + * @throws JSONException if the object does not contain enough information. + */ + public static String toString(JSONObject jo) throws JSONException { + Iterator keys = jo.keys(); + String string; + StringBuffer sb = new StringBuffer(); + if (jo.has("Fixes-Code") && jo.has("Reason-Phrase")) { + sb.append(jo.getString("HTTP-Version")); + sb.append(' '); + sb.append(jo.getString("Fixes-Code")); + sb.append(' '); + sb.append(jo.getString("Reason-Phrase")); + } else if (jo.has("Method") && jo.has("Request-URI")) { + sb.append(jo.getString("Method")); + sb.append(' '); + sb.append('"'); + sb.append(jo.getString("Request-URI")); + sb.append('"'); + sb.append(' '); + sb.append(jo.getString("HTTP-Version")); + } else { + throw new JSONException("Not enough material for an HTTP header."); + } + sb.append(CRLF); + while (keys.hasNext()) { + string = keys.next().toString(); + if (!string.equals("HTTP-Version") + && !string.equals("Fixes-Code") + && !string.equals("Reason-Phrase") + && !string.equals("Method") + && !string.equals("Request-URI") + && !jo.isNull(string)) { + sb.append(string); + sb.append(": "); + sb.append(jo.getString(string)); + sb.append(CRLF); + } + } + sb.append(CRLF); + return sb.toString(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/HTTPTokener.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/HTTPTokener.java index d318384..221c1fd 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/HTTPTokener.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/HTTPTokener.java @@ -17,56 +17,55 @@ package dev.brighten.antivpn.utils.json; /** - * The HTTPTokener extends the JSONTokener to provide additional methods - * for the parsing of HTTP headers. + * The HTTPTokener extends the JSONTokener to provide additional methods for the parsing of HTTP + * headers. * * @author JSON.org * @version 2010-12-24 */ public class HTTPTokener extends JSONTokener { - /** - * Construct an HTTPTokener from a string. - * - * @param string A source string. - */ - public HTTPTokener(String string) { - super(string); - } + /** + * Construct an HTTPTokener from a string. + * + * @param string A source string. + */ + public HTTPTokener(String string) { + super(string); + } - - /** - * Get the next token or string. This is used in parsing HTTP headers. - * - * @return A String. - * @throws JSONException - */ - public String nextToken() throws JSONException { - char c; - char q; - StringBuffer sb = new StringBuffer(); - do { - c = next(); - } while (Character.isWhitespace(c)); - if (c == '"' || c == '\'') { - q = c; - for (; ; ) { - c = next(); - if (c < ' ') { - throw syntaxError("Unterminated string."); - } - if (c == q) { - return sb.toString(); - } - sb.append(c); - } + /** + * Get the next token or string. This is used in parsing HTTP headers. + * + * @return A String. + * @throws JSONException + */ + public String nextToken() throws JSONException { + char c; + char q; + StringBuffer sb = new StringBuffer(); + do { + c = next(); + } while (Character.isWhitespace(c)); + if (c == '"' || c == '\'') { + q = c; + for (; ; ) { + c = next(); + if (c < ' ') { + throw syntaxError("Unterminated string."); } - for (; ; ) { - if (c == 0 || Character.isWhitespace(c)) { - return sb.toString(); - } - sb.append(c); - c = next(); + if (c == q) { + return sb.toString(); } + sb.append(c); + } } + for (; ; ) { + if (c == 0 || Character.isWhitespace(c)) { + return sb.toString(); + } + sb.append(c); + c = next(); + } + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONArray.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONArray.java index 8ab4bfc..81bae17 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONArray.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONArray.java @@ -25,48 +25,41 @@ import java.util.Iterator; import java.util.Map; /** - * A JSONArray is an ordered sequence of values. Its external text form is a - * string wrapped in square brackets with commas separating the values. The - * internal form is an object having get and opt - * methods for accessing the values by index, and put methods for - * adding or replacing values. The values can be any of these types: - * Boolean, JSONArray, JSONObject, - * Number, String, or the - * JSONObject.NULL object. - *

- * The constructor can convert a JSON text into a Java object. The - * toString method converts to JSON text. - *

- * A get method returns a value if one can be found, and throws an - * exception if one cannot be found. An opt method returns a - * default value instead of throwing an exception, and so is useful for - * obtaining optional values. - *

- * The generic get() and opt() methods return an - * object which you can cast or query for type. There are also typed - * get and opt methods that do type checking and type - * coercion for you. - *

- * The texts produced by the toString methods strictly conform to - * JSON syntax rules. The constructors are more forgiving in the texts they will - * accept: + * A JSONArray is an ordered sequence of values. Its external text form is a string wrapped in + * square brackets with commas separating the values. The internal form is an object having + * get and opt methods for accessing the values by index, and put + * methods for adding or replacing values. The values can be any of these types: Boolean + * , JSONArray, JSONObject, Number, String + * , or the JSONObject.NULL object. + * + *

The constructor can convert a JSON text into a Java object. The toString method + * converts to JSON text. + * + *

A get method returns a value if one can be found, and throws an exception if one + * cannot be found. An opt method returns a default value instead of throwing an + * exception, and so is useful for obtaining optional values. + * + *

The generic get() and opt() methods return an object which you can + * cast or query for type. There are also typed get and opt methods that + * do type checking and type coercion for you. + * + *

The texts produced by the toString methods strictly conform to JSON syntax rules. + * The constructors are more forgiving in the texts they will accept: + * *

    - *
  • An extra , (comma) may appear just - * before the closing bracket.
  • - *
  • The null value will be inserted when there - * is , (comma) elision.
  • - *
  • Strings may be quoted with ' (single - * quote).
  • - *
  • Strings do not need to be quoted at all if they do not begin with a quote - * or single quote, and if they do not contain leading or trailing spaces, - * and if they do not contain any of these characters: - * { } [ ] / \ : , = ; # and if they do not look like numbers - * and if they are not the reserved words true, - * false, or null.
  • - *
  • Values can be separated by ; (semicolon) as - * well as by , (comma).
  • - *
  • Numbers may have the - * 0x- (hex) prefix.
  • + *
  • An extra , (comma) may appear just before the closing + * bracket. + *
  • The null value will be inserted when there is , + *  (comma) elision. + *
  • Strings may be quoted with ' (single quote). + *
  • Strings do not need to be quoted at all if they do not begin with a quote or single quote, + * and if they do not contain leading or trailing spaces, and if they do not contain any of + * these characters: { } [ ] / \ : , = ; # and if they do not look like numbers + * and if they are not the reserved words true, false, or null + * . + *
  • Values can be separated by ; (semicolon) as well as by , + * (comma). + *
  • Numbers may have the 0x- (hex) prefix. *
* * @author JSON.org @@ -74,865 +67,766 @@ import java.util.Map; */ public class JSONArray { + /** The arrayList where the JSONArray's properties are kept. */ + private ArrayList myArrayList; - /** - * The arrayList where the JSONArray's properties are kept. - */ - private ArrayList myArrayList; + /** Construct an empty JSONArray. */ + public JSONArray() { + this.myArrayList = new ArrayList(); + } - - /** - * Construct an empty JSONArray. - */ - public JSONArray() { - this.myArrayList = new ArrayList(); + /** + * Construct a JSONArray from a JSONTokener. + * + * @param x A JSONTokener + * @throws JSONException If there is a syntax error. + */ + public JSONArray(JSONTokener x) throws JSONException { + this(); + if (x.nextClean() != '[') { + throw x.syntaxError("A JSONArray text must start with '['"); } - - /** - * Construct a JSONArray from a JSONTokener. - * - * @param x A JSONTokener - * @throws JSONException If there is a syntax error. - */ - public JSONArray(JSONTokener x) throws JSONException { - this(); - if (x.nextClean() != '[') { - throw x.syntaxError("A JSONArray text must start with '['"); + if (x.nextClean() != ']') { + x.back(); + for (; ; ) { + if (x.nextClean() == ',') { + x.back(); + this.myArrayList.add(JSONObject.NULL); + } else { + x.back(); + this.myArrayList.add(x.nextValue()); } - if (x.nextClean() != ']') { + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == ']') { + return; + } x.back(); - for (; ; ) { - if (x.nextClean() == ',') { - x.back(); - this.myArrayList.add(JSONObject.NULL); - } else { - x.back(); - this.myArrayList.add(x.nextValue()); - } - switch (x.nextClean()) { - case ';': - case ',': - if (x.nextClean() == ']') { - return; - } - x.back(); - break; - case ']': - return; - default: - throw x.syntaxError("Expected a ',' or ']'"); - } - } + break; + case ']': + return; + default: + throw x.syntaxError("Expected a ',' or ']'"); } + } } + } + /** + * Construct a JSONArray from a source JSON text. + * + * @param source A string that begins with [ (left bracket) and + * ends with ] (right bracket). + * @throws JSONException If there is a syntax error. + */ + public JSONArray(String source) throws JSONException { + this(new JSONTokener(source)); + } - /** - * Construct a JSONArray from a source JSON text. - * - * @param source A string that begins with - * [ (left bracket) - * and ends with ] (right bracket). - * @throws JSONException If there is a syntax error. - */ - public JSONArray(String source) throws JSONException { - this(new JSONTokener(source)); + /** + * Construct a JSONArray from a Collection. + * + * @param collection A Collection. + */ + public JSONArray(Collection collection) { + this.myArrayList = new ArrayList(); + if (collection != null) { + Iterator iter = collection.iterator(); + while (iter.hasNext()) { + this.myArrayList.add(JSONObject.wrap(iter.next())); + } } + } + /** + * Construct a JSONArray from an array + * + * @throws JSONException If not an array. + */ + public JSONArray(Object array) throws JSONException { + this(); + if (array.getClass().isArray()) { + int length = Array.getLength(array); + for (int i = 0; i < length; i += 1) { + this.put(JSONObject.wrap(Array.get(array, i))); + } + } else { + throw new JSONException("JSONArray initial value should be a string or collection or array."); + } + } - /** - * Construct a JSONArray from a Collection. - * - * @param collection A Collection. - */ - public JSONArray(Collection collection) { - this.myArrayList = new ArrayList(); - if (collection != null) { - Iterator iter = collection.iterator(); - while (iter.hasNext()) { - this.myArrayList.add(JSONObject.wrap(iter.next())); - } + /** + * Get the object value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return An object value. + * @throws JSONException If there is no value for the index. + */ + public Object get(int index) throws JSONException { + Object object = opt(index); + if (object == null) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + return object; + } + + /** + * Get the boolean value associated with an index. The string values "true" and "false" are + * converted to boolean. + * + * @param index The index must be between 0 and length() - 1. + * @return The truth. + * @throws JSONException If there is no value for the index or if the value is not convertible to + * boolean. + */ + public boolean getBoolean(int index) throws JSONException { + Object object = get(index); + if (object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object).equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object).equalsIgnoreCase("true"))) { + return true; + } + throw new JSONException("JSONArray[" + index + "] is not a boolean."); + } + + /** + * Get the double value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value cannot be converted to a number. + */ + public double getDouble(int index) throws JSONException { + Object object = get(index); + try { + return object instanceof Number + ? ((Number) object).doubleValue() + : Double.parseDouble((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the int value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value is not a number. + */ + public int getInt(int index) throws JSONException { + Object object = get(index); + try { + return object instanceof Number + ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the JSONArray associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return A JSONArray value. + * @throws JSONException If there is no value for the index. or if the value is not a JSONArray + */ + public JSONArray getJSONArray(int index) throws JSONException { + Object object = get(index); + if (object instanceof JSONArray) { + return (JSONArray) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONArray."); + } + + /** + * Get the JSONObject associated with an index. + * + * @param index subscript + * @return A JSONObject value. + * @throws JSONException If there is no value for the index or if the value is not a JSONObject + */ + public JSONObject getJSONObject(int index) throws JSONException { + Object object = get(index); + if (object instanceof JSONObject) { + return (JSONObject) object; + } + throw new JSONException("JSONArray[" + index + "] is not a JSONObject."); + } + + /** + * Get the long value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + * @throws JSONException If the key is not found or if the value cannot be converted to a number. + */ + public long getLong(int index) throws JSONException { + Object object = get(index); + try { + return object instanceof Number + ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) { + throw new JSONException("JSONArray[" + index + "] is not a number."); + } + } + + /** + * Get the string associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return A string value. + * @throws JSONException If there is no string value for the index. + */ + public String getString(int index) throws JSONException { + Object object = get(index); + if (object instanceof String) { + return (String) object; + } + throw new JSONException("JSONArray[" + index + "] not a string."); + } + + /** + * Determine if the value is null. + * + * @param index The index must be between 0 and length() - 1. + * @return true if the value at the index is null, or if there is no value. + */ + public boolean isNull(int index) { + return JSONObject.NULL.equals(opt(index)); + } + + /** + * Make a string from the contents of this JSONArray. The separator string is + * inserted between each element. Warning: This method assumes that the data structure is + * acyclical. + * + * @param separator A string that will be inserted between the elements. + * @return a string. + * @throws JSONException If the array contains an invalid number. + */ + public String join(String separator) throws JSONException { + int len = length(); + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < len; i += 1) { + if (i > 0) { + sb.append(separator); + } + sb.append(JSONObject.valueToString(this.myArrayList.get(i))); + } + return sb.toString(); + } + + /** + * Get the number of elements in the JSONArray, included nulls. + * + * @return The length (or size). + */ + public int length() { + return this.myArrayList.size(); + } + + /** + * Get the optional object value associated with an index. + * + * @param index The index must be between 0 and length() - 1. + * @return An object value, or null if there is no object at that index. + */ + public Object opt(int index) { + return (index < 0 || index >= length()) ? null : this.myArrayList.get(index); + } + + /** + * Get the optional boolean value associated with an index. It returns false if there is no value + * at that index, or if the value is not Boolean.TRUE or the String "true". + * + * @param index The index must be between 0 and length() - 1. + * @return The truth. + */ + public boolean optBoolean(int index) { + return optBoolean(index, false); + } + + /** + * Get the optional boolean value associated with an index. It returns the defaultValue if there + * is no value at that index or if it is not a Boolean or the String "true" or "false" (case + * insensitive). + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue A boolean default. + * @return The truth. + */ + public boolean optBoolean(int index, boolean defaultValue) { + try { + return getBoolean(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional double value associated with an index. NaN is returned if there is no value + * for the index, or if the value is not a number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public double optDouble(int index) { + return optDouble(index, Double.NaN); + } + + /** + * Get the optional double value associated with an index. The defaultValue is returned if there + * is no value for the index, or if the value is not a number and cannot be converted to a number. + * + * @param index subscript + * @param defaultValue The default value. + * @return The value. + */ + public double optDouble(int index, double defaultValue) { + try { + return getDouble(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional int value associated with an index. Zero is returned if there is no value for + * the index, or if the value is not a number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public int optInt(int index) { + return optInt(index, 0); + } + + /** + * Get the optional int value associated with an index. The defaultValue is returned if there is + * no value for the index, or if the value is not a number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return The value. + */ + public int optInt(int index, int defaultValue) { + try { + return getInt(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional JSONArray associated with an index. + * + * @param index subscript + * @return A JSONArray value, or null if the index has no value, or if the value is not a + * JSONArray. + */ + public JSONArray optJSONArray(int index) { + Object o = opt(index); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get the optional JSONObject associated with an index. Null is returned if the key is not found, + * or null if the index has no value, or if the value is not a JSONObject. + * + * @param index The index must be between 0 and length() - 1. + * @return A JSONObject value. + */ + public JSONObject optJSONObject(int index) { + Object o = opt(index); + return o instanceof JSONObject ? (JSONObject) o : null; + } + + /** + * Get the optional long value associated with an index. Zero is returned if there is no value for + * the index, or if the value is not a number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @return The value. + */ + public long optLong(int index) { + return optLong(index, 0); + } + + /** + * Get the optional long value associated with an index. The defaultValue is returned if there is + * no value for the index, or if the value is not a number and cannot be converted to a number. + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return The value. + */ + public long optLong(int index, long defaultValue) { + try { + return getLong(index); + } catch (Exception e) { + return defaultValue; + } + } + + /** + * Get the optional string value associated with an index. It returns an empty string if there is + * no value at that index. If the value is not a string and is not null, then it is coverted to a + * string. + * + * @param index The index must be between 0 and length() - 1. + * @return A String value. + */ + public String optString(int index) { + return optString(index, ""); + } + + /** + * Get the optional string associated with an index. The defaultValue is returned if the key is + * not found. + * + * @param index The index must be between 0 and length() - 1. + * @param defaultValue The default value. + * @return A String value. + */ + public String optString(int index, String defaultValue) { + Object object = opt(index); + return object != null ? object.toString() : defaultValue; + } + + /** + * Append a boolean value. This increases the array's length by one. + * + * @param value A boolean value. + * @return this. + */ + public JSONArray put(boolean value) { + put(value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which is produced from a + * Collection. + * + * @param value A Collection value. + * @return this. + */ + public JSONArray put(Collection value) { + put(new JSONArray(value)); + return this; + } + + /** + * Append a double value. This increases the array's length by one. + * + * @param value A double value. + * @return this. + * @throws JSONException if the value is not finite. + */ + public JSONArray put(double value) throws JSONException { + Double d = new Double(value); + JSONObject.testValidity(d); + put(d); + return this; + } + + /** + * Append an int value. This increases the array's length by one. + * + * @param value An int value. + * @return this. + */ + public JSONArray put(int value) { + put(new Integer(value)); + return this; + } + + /** + * Append an long value. This increases the array's length by one. + * + * @param value A long value. + * @return this. + */ + public JSONArray put(long value) { + put(new Long(value)); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject which is produced from a + * Map. + * + * @param value A Map value. + * @return this. + */ + public JSONArray put(Map value) { + put(new JSONObject(value)); + return this; + } + + /** + * Append an object value. This increases the array's length by one. + * + * @param value An object value. The value should be a Boolean, Double, Integer, JSONArray, + * JSONObject, Long, or String, or the JSONObject.NULL object. + * @return this. + */ + public JSONArray put(Object value) { + this.myArrayList.add(value); + return this; + } + + /** + * Put or replace a boolean value in the JSONArray. If the index is greater than the length of the + * JSONArray, then null elements will be added as necessary to pad it out. + * + * @param index The subscript. + * @param value A boolean value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, boolean value) throws JSONException { + put(index, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONArray which is produced from a + * Collection. + * + * @param index The subscript. + * @param value A Collection value. + * @return this. + * @throws JSONException If the index is negative or if the value is not finite. + */ + public JSONArray put(int index, Collection value) throws JSONException { + put(index, new JSONArray(value)); + return this; + } + + /** + * Put or replace a double value. If the index is greater than the length of the JSONArray, then + * null elements will be added as necessary to pad it out. + * + * @param index The subscript. + * @param value A double value. + * @return this. + * @throws JSONException If the index is negative or if the value is not finite. + */ + public JSONArray put(int index, double value) throws JSONException { + put(index, new Double(value)); + return this; + } + + /** + * Put or replace an int value. If the index is greater than the length of the JSONArray, then + * null elements will be added as necessary to pad it out. + * + * @param index The subscript. + * @param value An int value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, int value) throws JSONException { + put(index, new Integer(value)); + return this; + } + + /** + * Put or replace a long value. If the index is greater than the length of the JSONArray, then + * null elements will be added as necessary to pad it out. + * + * @param index The subscript. + * @param value A long value. + * @return this. + * @throws JSONException If the index is negative. + */ + public JSONArray put(int index, long value) throws JSONException { + put(index, new Long(value)); + return this; + } + + /** + * Put a value in the JSONArray, where the value will be a JSONObject that is produced from a Map. + * + * @param index The subscript. + * @param value The Map value. + * @return this. + * @throws JSONException If the index is negative or if the the value is an invalid number. + */ + public JSONArray put(int index, Map value) throws JSONException { + put(index, new JSONObject(value)); + return this; + } + + /** + * Put or replace an object value in the JSONArray. If the index is greater than the length of the + * JSONArray, then null elements will be added as necessary to pad it out. + * + * @param index The subscript. + * @param value The value to put into the array. The value should be a Boolean, Double, Integer, + * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException If the index is negative or if the the value is an invalid number. + */ + public JSONArray put(int index, Object value) throws JSONException { + JSONObject.testValidity(value); + if (index < 0) { + throw new JSONException("JSONArray[" + index + "] not found."); + } + if (index < length()) { + this.myArrayList.set(index, value); + } else { + while (index != length()) { + put(JSONObject.NULL); + } + put(value); + } + return this; + } + + /** + * Remove an index and close the hole. + * + * @param index The index of the element to be removed. + * @return The value that was associated with the index, or null if there was no value. + */ + public Object remove(int index) { + Object o = opt(index); + this.myArrayList.remove(index); + return o; + } + + /** + * Produce a JSONObject by combining a JSONArray of names with the values of this JSONArray. + * + * @param names A JSONArray containing a list of key strings. These will be paired with the + * values. + * @return A JSONObject, or null if there are no names or if this JSONArray has no values. + * @throws JSONException If any of the names are null. + */ + public JSONObject toJSONObject(JSONArray names) throws JSONException { + if (names == null || names.length() == 0 || length() == 0) { + return null; + } + JSONObject jo = new JSONObject(); + for (int i = 0; i < names.length(); i += 1) { + jo.put(names.getString(i), this.opt(i)); + } + return jo; + } + + /** + * Make a JSON text of this JSONArray. For compactness, no unnecessary whitespace is added. If it + * is not possible to produce a syntactically correct JSON text then null will be returned + * instead. This could occur if the array contains an invalid number. + * + *

Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, transmittable representation of the array. + */ + public String toString() { + try { + return '[' + join(",") + ']'; + } catch (Exception e) { + return null; + } + } + + /** + * Make a prettyprinted JSON text of this JSONArray. Warning: This method assumes that the data + * structure is acyclical. + * + * @param indentFactor The number of spaces to add to each level of indentation. + * @return a printable, displayable, transmittable representation of the object, beginning with + * [ (left bracket) and ending with ] + *  (right bracket). + * @throws JSONException + */ + public String toString(int indentFactor) throws JSONException { + return toString(indentFactor, 0); + } + + /** + * Make a prettyprinted JSON text of this JSONArray. Warning: This method assumes that the data + * structure is acyclical. + * + * @param indentFactor The number of spaces to add to each level of indentation. + * @param indent The indention of the top level. + * @return a printable, displayable, transmittable representation of the array. + * @throws JSONException + */ + String toString(int indentFactor, int indent) throws JSONException { + int len = length(); + if (len == 0) { + return "[]"; + } + int i; + StringBuffer sb = new StringBuffer("["); + if (len == 1) { + sb.append(JSONObject.valueToString(this.myArrayList.get(0), indentFactor, indent)); + } else { + int newindent = indent + indentFactor; + sb.append('\n'); + for (i = 0; i < len; i += 1) { + if (i > 0) { + sb.append(",\n"); } + for (int j = 0; j < newindent; j += 1) { + sb.append(' '); + } + sb.append(JSONObject.valueToString(this.myArrayList.get(i), indentFactor, newindent)); + } + sb.append('\n'); + for (i = 0; i < indent; i += 1) { + sb.append(' '); + } } + sb.append(']'); + return sb.toString(); + } + /** + * Write the contents of the JSONArray as JSON text to a writer. For compactness, no whitespace is + * added. + * + *

Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + try { + boolean b = false; + int len = length(); - /** - * Construct a JSONArray from an array - * - * @throws JSONException If not an array. - */ - public JSONArray(Object array) throws JSONException { - this(); - if (array.getClass().isArray()) { - int length = Array.getLength(array); - for (int i = 0; i < length; i += 1) { - this.put(JSONObject.wrap(Array.get(array, i))); - } + writer.write('['); + + for (int i = 0; i < len; i += 1) { + if (b) { + writer.write(','); + } + Object v = this.myArrayList.get(i); + if (v instanceof JSONObject) { + ((JSONObject) v).write(writer); + } else if (v instanceof JSONArray) { + ((JSONArray) v).write(writer); } else { - throw new JSONException( - "JSONArray initial value should be a string or collection or array."); + writer.write(JSONObject.valueToString(v)); } + b = true; + } + writer.write(']'); + return writer; + } catch (IOException e) { + throw new JSONException(e); } - - - /** - * Get the object value associated with an index. - * - * @param index The index must be between 0 and length() - 1. - * @return An object value. - * @throws JSONException If there is no value for the index. - */ - public Object get(int index) throws JSONException { - Object object = opt(index); - if (object == null) { - throw new JSONException("JSONArray[" + index + "] not found."); - } - return object; - } - - - /** - * Get the boolean value associated with an index. - * The string values "true" and "false" are converted to boolean. - * - * @param index The index must be between 0 and length() - 1. - * @return The truth. - * @throws JSONException If there is no value for the index or if the - * value is not convertible to boolean. - */ - public boolean getBoolean(int index) throws JSONException { - Object object = get(index); - if (object.equals(Boolean.FALSE) || - (object instanceof String && - ((String) object).equalsIgnoreCase("false"))) { - return false; - } else if (object.equals(Boolean.TRUE) || - (object instanceof String && - ((String) object).equalsIgnoreCase("true"))) { - return true; - } - throw new JSONException("JSONArray[" + index + "] is not a boolean."); - } - - - /** - * Get the double value associated with an index. - * - * @param index The index must be between 0 and length() - 1. - * @return The value. - * @throws JSONException If the key is not found or if the value cannot - * be converted to a number. - */ - public double getDouble(int index) throws JSONException { - Object object = get(index); - try { - return object instanceof Number ? - ((Number) object).doubleValue() : - Double.parseDouble((String) object); - } catch (Exception e) { - throw new JSONException("JSONArray[" + index + - "] is not a number."); - } - } - - - /** - * Get the int value associated with an index. - * - * @param index The index must be between 0 and length() - 1. - * @return The value. - * @throws JSONException If the key is not found or if the value is not a number. - */ - public int getInt(int index) throws JSONException { - Object object = get(index); - try { - return object instanceof Number ? - ((Number) object).intValue() : - Integer.parseInt((String) object); - } catch (Exception e) { - throw new JSONException("JSONArray[" + index + - "] is not a number."); - } - } - - - /** - * Get the JSONArray associated with an index. - * - * @param index The index must be between 0 and length() - 1. - * @return A JSONArray value. - * @throws JSONException If there is no value for the index. or if the - * value is not a JSONArray - */ - public JSONArray getJSONArray(int index) throws JSONException { - Object object = get(index); - if (object instanceof JSONArray) { - return (JSONArray) object; - } - throw new JSONException("JSONArray[" + index + - "] is not a JSONArray."); - } - - - /** - * Get the JSONObject associated with an index. - * - * @param index subscript - * @return A JSONObject value. - * @throws JSONException If there is no value for the index or if the - * value is not a JSONObject - */ - public JSONObject getJSONObject(int index) throws JSONException { - Object object = get(index); - if (object instanceof JSONObject) { - return (JSONObject) object; - } - throw new JSONException("JSONArray[" + index + - "] is not a JSONObject."); - } - - - /** - * Get the long value associated with an index. - * - * @param index The index must be between 0 and length() - 1. - * @return The value. - * @throws JSONException If the key is not found or if the value cannot - * be converted to a number. - */ - public long getLong(int index) throws JSONException { - Object object = get(index); - try { - return object instanceof Number ? - ((Number) object).longValue() : - Long.parseLong((String) object); - } catch (Exception e) { - throw new JSONException("JSONArray[" + index + - "] is not a number."); - } - } - - - /** - * Get the string associated with an index. - * - * @param index The index must be between 0 and length() - 1. - * @return A string value. - * @throws JSONException If there is no string value for the index. - */ - public String getString(int index) throws JSONException { - Object object = get(index); - if (object instanceof String) { - return (String) object; - } - throw new JSONException("JSONArray[" + index + "] not a string."); - } - - - /** - * Determine if the value is null. - * - * @param index The index must be between 0 and length() - 1. - * @return true if the value at the index is null, or if there is no value. - */ - public boolean isNull(int index) { - return JSONObject.NULL.equals(opt(index)); - } - - - /** - * Make a string from the contents of this JSONArray. The - * separator string is inserted between each element. - * Warning: This method assumes that the data structure is acyclical. - * - * @param separator A string that will be inserted between the elements. - * @return a string. - * @throws JSONException If the array contains an invalid number. - */ - public String join(String separator) throws JSONException { - int len = length(); - StringBuffer sb = new StringBuffer(); - - for (int i = 0; i < len; i += 1) { - if (i > 0) { - sb.append(separator); - } - sb.append(JSONObject.valueToString(this.myArrayList.get(i))); - } - return sb.toString(); - } - - - /** - * Get the number of elements in the JSONArray, included nulls. - * - * @return The length (or size). - */ - public int length() { - return this.myArrayList.size(); - } - - - /** - * Get the optional object value associated with an index. - * - * @param index The index must be between 0 and length() - 1. - * @return An object value, or null if there is no - * object at that index. - */ - public Object opt(int index) { - return (index < 0 || index >= length()) ? - null : this.myArrayList.get(index); - } - - - /** - * Get the optional boolean value associated with an index. - * It returns false if there is no value at that index, - * or if the value is not Boolean.TRUE or the String "true". - * - * @param index The index must be between 0 and length() - 1. - * @return The truth. - */ - public boolean optBoolean(int index) { - return optBoolean(index, false); - } - - - /** - * Get the optional boolean value associated with an index. - * It returns the defaultValue if there is no value at that index or if - * it is not a Boolean or the String "true" or "false" (case insensitive). - * - * @param index The index must be between 0 and length() - 1. - * @param defaultValue A boolean default. - * @return The truth. - */ - public boolean optBoolean(int index, boolean defaultValue) { - try { - return getBoolean(index); - } catch (Exception e) { - return defaultValue; - } - } - - - /** - * Get the optional double value associated with an index. - * NaN is returned if there is no value for the index, - * or if the value is not a number and cannot be converted to a number. - * - * @param index The index must be between 0 and length() - 1. - * @return The value. - */ - public double optDouble(int index) { - return optDouble(index, Double.NaN); - } - - - /** - * Get the optional double value associated with an index. - * The defaultValue is returned if there is no value for the index, - * or if the value is not a number and cannot be converted to a number. - * - * @param index subscript - * @param defaultValue The default value. - * @return The value. - */ - public double optDouble(int index, double defaultValue) { - try { - return getDouble(index); - } catch (Exception e) { - return defaultValue; - } - } - - - /** - * Get the optional int value associated with an index. - * Zero is returned if there is no value for the index, - * or if the value is not a number and cannot be converted to a number. - * - * @param index The index must be between 0 and length() - 1. - * @return The value. - */ - public int optInt(int index) { - return optInt(index, 0); - } - - - /** - * Get the optional int value associated with an index. - * The defaultValue is returned if there is no value for the index, - * or if the value is not a number and cannot be converted to a number. - * - * @param index The index must be between 0 and length() - 1. - * @param defaultValue The default value. - * @return The value. - */ - public int optInt(int index, int defaultValue) { - try { - return getInt(index); - } catch (Exception e) { - return defaultValue; - } - } - - - /** - * Get the optional JSONArray associated with an index. - * - * @param index subscript - * @return A JSONArray value, or null if the index has no value, - * or if the value is not a JSONArray. - */ - public JSONArray optJSONArray(int index) { - Object o = opt(index); - return o instanceof JSONArray ? (JSONArray) o : null; - } - - - /** - * Get the optional JSONObject associated with an index. - * Null is returned if the key is not found, or null if the index has - * no value, or if the value is not a JSONObject. - * - * @param index The index must be between 0 and length() - 1. - * @return A JSONObject value. - */ - public JSONObject optJSONObject(int index) { - Object o = opt(index); - return o instanceof JSONObject ? (JSONObject) o : null; - } - - - /** - * Get the optional long value associated with an index. - * Zero is returned if there is no value for the index, - * or if the value is not a number and cannot be converted to a number. - * - * @param index The index must be between 0 and length() - 1. - * @return The value. - */ - public long optLong(int index) { - return optLong(index, 0); - } - - - /** - * Get the optional long value associated with an index. - * The defaultValue is returned if there is no value for the index, - * or if the value is not a number and cannot be converted to a number. - * - * @param index The index must be between 0 and length() - 1. - * @param defaultValue The default value. - * @return The value. - */ - public long optLong(int index, long defaultValue) { - try { - return getLong(index); - } catch (Exception e) { - return defaultValue; - } - } - - - /** - * Get the optional string value associated with an index. It returns an - * empty string if there is no value at that index. If the value - * is not a string and is not null, then it is coverted to a string. - * - * @param index The index must be between 0 and length() - 1. - * @return A String value. - */ - public String optString(int index) { - return optString(index, ""); - } - - - /** - * Get the optional string associated with an index. - * The defaultValue is returned if the key is not found. - * - * @param index The index must be between 0 and length() - 1. - * @param defaultValue The default value. - * @return A String value. - */ - public String optString(int index, String defaultValue) { - Object object = opt(index); - return object != null ? object.toString() : defaultValue; - } - - - /** - * Append a boolean value. This increases the array's length by one. - * - * @param value A boolean value. - * @return this. - */ - public JSONArray put(boolean value) { - put(value ? Boolean.TRUE : Boolean.FALSE); - return this; - } - - - /** - * Put a value in the JSONArray, where the value will be a - * JSONArray which is produced from a Collection. - * - * @param value A Collection value. - * @return this. - */ - public JSONArray put(Collection value) { - put(new JSONArray(value)); - return this; - } - - - /** - * Append a double value. This increases the array's length by one. - * - * @param value A double value. - * @return this. - * @throws JSONException if the value is not finite. - */ - public JSONArray put(double value) throws JSONException { - Double d = new Double(value); - JSONObject.testValidity(d); - put(d); - return this; - } - - - /** - * Append an int value. This increases the array's length by one. - * - * @param value An int value. - * @return this. - */ - public JSONArray put(int value) { - put(new Integer(value)); - return this; - } - - - /** - * Append an long value. This increases the array's length by one. - * - * @param value A long value. - * @return this. - */ - public JSONArray put(long value) { - put(new Long(value)); - return this; - } - - - /** - * Put a value in the JSONArray, where the value will be a - * JSONObject which is produced from a Map. - * - * @param value A Map value. - * @return this. - */ - public JSONArray put(Map value) { - put(new JSONObject(value)); - return this; - } - - - /** - * Append an object value. This increases the array's length by one. - * - * @param value An object value. The value should be a - * Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the - * JSONObject.NULL object. - * @return this. - */ - public JSONArray put(Object value) { - this.myArrayList.add(value); - return this; - } - - - /** - * Put or replace a boolean value in the JSONArray. If the index is greater - * than the length of the JSONArray, then null elements will be added as - * necessary to pad it out. - * - * @param index The subscript. - * @param value A boolean value. - * @return this. - * @throws JSONException If the index is negative. - */ - public JSONArray put(int index, boolean value) throws JSONException { - put(index, value ? Boolean.TRUE : Boolean.FALSE); - return this; - } - - - /** - * Put a value in the JSONArray, where the value will be a - * JSONArray which is produced from a Collection. - * - * @param index The subscript. - * @param value A Collection value. - * @return this. - * @throws JSONException If the index is negative or if the value is - * not finite. - */ - public JSONArray put(int index, Collection value) throws JSONException { - put(index, new JSONArray(value)); - return this; - } - - - /** - * Put or replace a double value. If the index is greater than the length of - * the JSONArray, then null elements will be added as necessary to pad - * it out. - * - * @param index The subscript. - * @param value A double value. - * @return this. - * @throws JSONException If the index is negative or if the value is - * not finite. - */ - public JSONArray put(int index, double value) throws JSONException { - put(index, new Double(value)); - return this; - } - - - /** - * Put or replace an int value. If the index is greater than the length of - * the JSONArray, then null elements will be added as necessary to pad - * it out. - * - * @param index The subscript. - * @param value An int value. - * @return this. - * @throws JSONException If the index is negative. - */ - public JSONArray put(int index, int value) throws JSONException { - put(index, new Integer(value)); - return this; - } - - - /** - * Put or replace a long value. If the index is greater than the length of - * the JSONArray, then null elements will be added as necessary to pad - * it out. - * - * @param index The subscript. - * @param value A long value. - * @return this. - * @throws JSONException If the index is negative. - */ - public JSONArray put(int index, long value) throws JSONException { - put(index, new Long(value)); - return this; - } - - - /** - * Put a value in the JSONArray, where the value will be a - * JSONObject that is produced from a Map. - * - * @param index The subscript. - * @param value The Map value. - * @return this. - * @throws JSONException If the index is negative or if the the value is - * an invalid number. - */ - public JSONArray put(int index, Map value) throws JSONException { - put(index, new JSONObject(value)); - return this; - } - - - /** - * Put or replace an object value in the JSONArray. If the index is greater - * than the length of the JSONArray, then null elements will be added as - * necessary to pad it out. - * - * @param index The subscript. - * @param value The value to put into the array. The value should be a - * Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the - * JSONObject.NULL object. - * @return this. - * @throws JSONException If the index is negative or if the the value is - * an invalid number. - */ - public JSONArray put(int index, Object value) throws JSONException { - JSONObject.testValidity(value); - if (index < 0) { - throw new JSONException("JSONArray[" + index + "] not found."); - } - if (index < length()) { - this.myArrayList.set(index, value); - } else { - while (index != length()) { - put(JSONObject.NULL); - } - put(value); - } - return this; - } - - - /** - * Remove an index and close the hole. - * - * @param index The index of the element to be removed. - * @return The value that was associated with the index, - * or null if there was no value. - */ - public Object remove(int index) { - Object o = opt(index); - this.myArrayList.remove(index); - return o; - } - - - /** - * Produce a JSONObject by combining a JSONArray of names with the values - * of this JSONArray. - * - * @param names A JSONArray containing a list of key strings. These will be - * paired with the values. - * @return A JSONObject, or null if there are no names or if this JSONArray - * has no values. - * @throws JSONException If any of the names are null. - */ - public JSONObject toJSONObject(JSONArray names) throws JSONException { - if (names == null || names.length() == 0 || length() == 0) { - return null; - } - JSONObject jo = new JSONObject(); - for (int i = 0; i < names.length(); i += 1) { - jo.put(names.getString(i), this.opt(i)); - } - return jo; - } - - - /** - * Make a JSON text of this JSONArray. For compactness, no - * unnecessary whitespace is added. If it is not possible to produce a - * syntactically correct JSON text then null will be returned instead. This - * could occur if the array contains an invalid number. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @return a printable, displayable, transmittable - * representation of the array. - */ - public String toString() { - try { - return '[' + join(",") + ']'; - } catch (Exception e) { - return null; - } - } - - - /** - * Make a prettyprinted JSON text of this JSONArray. - * Warning: This method assumes that the data structure is acyclical. - * - * @param indentFactor The number of spaces to add to each level of - * indentation. - * @return a printable, displayable, transmittable - * representation of the object, beginning - * with [ (left bracket) and ending - * with ] (right bracket). - * @throws JSONException - */ - public String toString(int indentFactor) throws JSONException { - return toString(indentFactor, 0); - } - - - /** - * Make a prettyprinted JSON text of this JSONArray. - * Warning: This method assumes that the data structure is acyclical. - * - * @param indentFactor The number of spaces to add to each level of - * indentation. - * @param indent The indention of the top level. - * @return a printable, displayable, transmittable - * representation of the array. - * @throws JSONException - */ - String toString(int indentFactor, int indent) throws JSONException { - int len = length(); - if (len == 0) { - return "[]"; - } - int i; - StringBuffer sb = new StringBuffer("["); - if (len == 1) { - sb.append(JSONObject.valueToString(this.myArrayList.get(0), - indentFactor, indent)); - } else { - int newindent = indent + indentFactor; - sb.append('\n'); - for (i = 0; i < len; i += 1) { - if (i > 0) { - sb.append(",\n"); - } - for (int j = 0; j < newindent; j += 1) { - sb.append(' '); - } - sb.append(JSONObject.valueToString(this.myArrayList.get(i), - indentFactor, newindent)); - } - sb.append('\n'); - for (i = 0; i < indent; i += 1) { - sb.append(' '); - } - } - sb.append(']'); - return sb.toString(); - } - - - /** - * Write the contents of the JSONArray as JSON text to a writer. - * For compactness, no whitespace is added. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @return The writer. - * @throws JSONException - */ - public Writer write(Writer writer) throws JSONException { - try { - boolean b = false; - int len = length(); - - writer.write('['); - - for (int i = 0; i < len; i += 1) { - if (b) { - writer.write(','); - } - Object v = this.myArrayList.get(i); - if (v instanceof JSONObject) { - ((JSONObject) v).write(writer); - } else if (v instanceof JSONArray) { - ((JSONArray) v).write(writer); - } else { - writer.write(JSONObject.valueToString(v)); - } - b = true; - } - writer.write(']'); - return writer; - } catch (IOException e) { - throw new JSONException(e); - } - } -} \ No newline at end of file + } +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONException.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONException.java index 599e3b2..bfdd0e2 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONException.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONException.java @@ -23,24 +23,24 @@ package dev.brighten.antivpn.utils.json; * @version 2010-12-24 */ public class JSONException extends Exception { - private static final long serialVersionUID = 0; - private Throwable cause; + private static final long serialVersionUID = 0; + private Throwable cause; - /** - * Constructs a JSONException with an explanatory message. - * - * @param message Detail about the reason for the exception. - */ - public JSONException(String message) { - super(message); - } + /** + * Constructs a JSONException with an explanatory message. + * + * @param message Detail about the reason for the exception. + */ + public JSONException(String message) { + super(message); + } - public JSONException(Throwable cause) { - super(cause.getMessage()); - this.cause = cause; - } + public JSONException(Throwable cause) { + super(cause.getMessage()); + this.cause = cause; + } - public Throwable getCause() { - return this.cause; - } + public Throwable getCause() { + return this.cause; + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONML.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONML.java index f31c6f7..0d5ea9f 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONML.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONML.java @@ -18,439 +18,420 @@ package dev.brighten.antivpn.utils.json; import java.util.Iterator; - /** - * This provides static methods to convert an XML text into a JSONArray or - * JSONObject, and to covert a JSONArray or JSONObject into an XML text using - * the JsonML transform. + * This provides static methods to convert an XML text into a JSONArray or JSONObject, and to covert + * a JSONArray or JSONObject into an XML text using the JsonML transform. * * @author JSON.org * @version 2010-12-23 */ public class JSONML { - /** - * Parse XML values and store them in a JSONArray. - * - * @param x The XMLTokener containing the source string. - * @param arrayForm true if array form, false if object form. - * @param ja The JSONArray that is containing the current tag or null - * if we are at the outermost level. - * @return A JSONArray if the value is the outermost tag, otherwise null. - * @throws JSONException - */ - private static Object parse(XMLTokener x, boolean arrayForm, - JSONArray ja) throws JSONException { - String attribute; - char c; - String closeTag = null; - int i; - JSONArray newja = null; - JSONObject newjo = null; - Object token; - String tagName = null; + /** + * Parse XML values and store them in a JSONArray. + * + * @param x The XMLTokener containing the source string. + * @param arrayForm true if array form, false if object form. + * @param ja The JSONArray that is containing the current tag or null if we are at the outermost + * level. + * @return A JSONArray if the value is the outermost tag, otherwise null. + * @throws JSONException + */ + private static Object parse(XMLTokener x, boolean arrayForm, JSONArray ja) throws JSONException { + String attribute; + char c; + String closeTag = null; + int i; + JSONArray newja = null; + JSONObject newjo = null; + Object token; + String tagName = null; -// Test for and skip past these forms: -// -// -// -// + // Test for and skip past these forms: + // + // + // + // - while (true) { - token = x.nextContent(); - if (token == XML.LT) { - token = x.nextToken(); - if (token instanceof Character) { - if (token == XML.SLASH) { + while (true) { + token = x.nextContent(); + if (token == XML.LT) { + token = x.nextToken(); + if (token instanceof Character) { + if (token == XML.SLASH) { -// Close tag "); - } - x.back(); - } else if (c == '[') { - token = x.nextToken(); - if (token.equals("CDATA") && x.next() == '[') { - if (ja != null) { - ja.put(x.nextCDATA()); - } - } else { - throw x.syntaxError("Expected 'CDATA['"); - } - } else { - i = 1; - do { - token = x.nextMeta(); - if (token == null) { - throw x.syntaxError("Missing '>' after ' 0); - } - } else if (token == XML.QUEST) { - -// "); - } else { - throw x.syntaxError("Misshaped tag"); - } - -// Open tag < - - } else { - if (!(token instanceof String)) { - throw x.syntaxError("Bad tagName '" + token + "'."); - } - tagName = (String) token; - newja = new JSONArray(); - newjo = new JSONObject(); - if (arrayForm) { - newja.put(tagName); - if (ja != null) { - ja.put(newja); - } - } else { - newjo.put("tagName", tagName); - if (ja != null) { - ja.put(newjo); - } - } - token = null; - for (; ; ) { - if (token == null) { - token = x.nextToken(); - } - if (token == null) { - throw x.syntaxError("Misshaped tag"); - } - if (!(token instanceof String)) { - break; - } - -// attribute = value - - attribute = (String) token; - if (!arrayForm && (attribute == "tagName" || attribute == "childNode")) { - throw x.syntaxError("Reserved attribute."); - } - token = x.nextToken(); - if (token == XML.EQ) { - token = x.nextToken(); - if (!(token instanceof String)) { - throw x.syntaxError("Missing value"); - } - newjo.accumulate(attribute, XML.stringToValue((String) token)); - token = null; - } else { - newjo.accumulate(attribute, ""); - } - } - if (arrayForm && newjo.length() > 0) { - newja.put(newjo); - } - -// Empty tag <.../> - - if (token == XML.SLASH) { - if (x.nextToken() != XML.GT) { - throw x.syntaxError("Misshaped tag"); - } - if (ja == null) { - if (arrayForm) { - return newja; - } else { - return newjo; - } - } - -// Content, between <...> and - - } else { - if (token != XML.GT) { - throw x.syntaxError("Misshaped tag"); - } - closeTag = (String) parse(x, arrayForm, newja); - if (closeTag != null) { - if (!closeTag.equals(tagName)) { - throw x.syntaxError("Mismatched '" + tagName + - "' and '" + closeTag + "'"); - } - tagName = null; - if (!arrayForm && newja.length() > 0) { - newjo.put("childNodes", newja); - } - if (ja == null) { - if (arrayForm) { - return newja; - } else { - return newjo; - } - } - } - } - } - } else { + c = x.next(); + if (c == '-') { + if (x.next() == '-') { + x.skipPast("-->"); + } + x.back(); + } else if (c == '[') { + token = x.nextToken(); + if (token.equals("CDATA") && x.next() == '[') { if (ja != null) { - ja.put(token instanceof String ? - XML.stringToValue((String) token) : token); + ja.put(x.nextCDATA()); } - } - } - } - - - /** - * Convert a well-formed (but not necessarily valid) XML string into a - * JSONArray using the JsonML transform. Each XML tag is represented as - * a JSONArray in which the first element is the tag name. If the tag has - * attributes, then the second element will be JSONObject containing the - * name/value pairs. If the tag contains children, then strings and - * JSONArrays will represent the child tags. - * Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * - * @param string The source string. - * @return A JSONArray containing the structured data from the XML string. - * @throws JSONException - */ - public static JSONArray toJSONArray(String string) throws JSONException { - return toJSONArray(new XMLTokener(string)); - } - - - /** - * Convert a well-formed (but not necessarily valid) XML string into a - * JSONArray using the JsonML transform. Each XML tag is represented as - * a JSONArray in which the first element is the tag name. If the tag has - * attributes, then the second element will be JSONObject containing the - * name/value pairs. If the tag contains children, then strings and - * JSONArrays will represent the child content and tags. - * Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * - * @param x An XMLTokener. - * @return A JSONArray containing the structured data from the XML string. - * @throws JSONException - */ - public static JSONArray toJSONArray(XMLTokener x) throws JSONException { - return (JSONArray) parse(x, true, null); - } - - - /** - * Convert a well-formed (but not necessarily valid) XML string into a - * JSONObject using the JsonML transform. Each XML tag is represented as - * a JSONObject with a "tagName" property. If the tag has attributes, then - * the attributes will be in the JSONObject as properties. If the tag - * contains children, the object will have a "childNodes" property which - * will be an array of strings and JsonML JSONObjects. - *

- * Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * - * @param x An XMLTokener of the XML source text. - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException - */ - public static JSONObject toJSONObject(XMLTokener x) throws JSONException { - return (JSONObject) parse(x, false, null); - } - - - /** - * Convert a well-formed (but not necessarily valid) XML string into a - * JSONObject using the JsonML transform. Each XML tag is represented as - * a JSONObject with a "tagName" property. If the tag has attributes, then - * the attributes will be in the JSONObject as properties. If the tag - * contains children, the object will have a "childNodes" property which - * will be an array of strings and JsonML JSONObjects. - *

- * Comments, prologs, DTDs, and <[ [ ]]> are ignored. - * - * @param string The XML source text. - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException - */ - public static JSONObject toJSONObject(String string) throws JSONException { - return toJSONObject(new XMLTokener(string)); - } - - - /** - * Reverse the JSONML transformation, making an XML text from a JSONArray. - * - * @param ja A JSONArray. - * @return An XML string. - * @throws JSONException - */ - public static String toString(JSONArray ja) throws JSONException { - int i; - JSONObject jo; - String key; - Iterator keys; - int length; - Object object; - StringBuffer sb = new StringBuffer(); - String tagName; - String value; - -// Emit ' after ' 0); } + } else if (token == XML.QUEST) { + + // "); + } else { + throw x.syntaxError("Misshaped tag"); + } + + // Open tag < + } else { - i = 1; - } + if (!(token instanceof String)) { + throw x.syntaxError("Bad tagName '" + token + "'."); + } + tagName = (String) token; + newja = new JSONArray(); + newjo = new JSONObject(); + if (arrayForm) { + newja.put(tagName); + if (ja != null) { + ja.put(newja); + } + } else { + newjo.put("tagName", tagName); + if (ja != null) { + ja.put(newjo); + } + } + token = null; + for (; ; ) { + if (token == null) { + token = x.nextToken(); + } + if (token == null) { + throw x.syntaxError("Misshaped tag"); + } + if (!(token instanceof String)) { + break; + } -//Emit content in body + // attribute = value - length = ja.length(); - if (i >= length) { - sb.append('/'); - sb.append('>'); - } else { - sb.append('>'); - do { - object = ja.get(i); - i += 1; - if (object != null) { - if (object instanceof String) { - sb.append(XML.escape(object.toString())); - } else if (object instanceof JSONObject) { - sb.append(toString((JSONObject) object)); - } else if (object instanceof JSONArray) { - sb.append(toString((JSONArray) object)); - } + attribute = (String) token; + if (!arrayForm && (attribute == "tagName" || attribute == "childNode")) { + throw x.syntaxError("Reserved attribute."); + } + token = x.nextToken(); + if (token == XML.EQ) { + token = x.nextToken(); + if (!(token instanceof String)) { + throw x.syntaxError("Missing value"); + } + newjo.accumulate(attribute, XML.stringToValue((String) token)); + token = null; + } else { + newjo.accumulate(attribute, ""); + } + } + if (arrayForm && newjo.length() > 0) { + newja.put(newjo); + } + + // Empty tag <.../> + + if (token == XML.SLASH) { + if (x.nextToken() != XML.GT) { + throw x.syntaxError("Misshaped tag"); + } + if (ja == null) { + if (arrayForm) { + return newja; + } else { + return newjo; + } + } + + // Content, between <...> and + + } else { + if (token != XML.GT) { + throw x.syntaxError("Misshaped tag"); + } + closeTag = (String) parse(x, arrayForm, newja); + if (closeTag != null) { + if (!closeTag.equals(tagName)) { + throw x.syntaxError("Mismatched '" + tagName + "' and '" + closeTag + "'"); + } + tagName = null; + if (!arrayForm && newja.length() > 0) { + newjo.put("childNodes", newja); + } + if (ja == null) { + if (arrayForm) { + return newja; + } else { + return newjo; } - } while (i < length); - sb.append('<'); - sb.append('/'); - sb.append(tagName); - sb.append('>'); + } + } + } } - return sb.toString(); + } else { + if (ja != null) { + ja.put(token instanceof String ? XML.stringToValue((String) token) : token); + } + } + } + } + + /** + * Convert a well-formed (but not necessarily valid) XML string into a JSONArray using the JsonML + * transform. Each XML tag is represented as a JSONArray in which the first element is the tag + * name. If the tag has attributes, then the second element will be JSONObject containing the + * name/value pairs. If the tag contains children, then strings and JSONArrays will represent the + * child tags. Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * + * @param string The source string. + * @return A JSONArray containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONArray toJSONArray(String string) throws JSONException { + return toJSONArray(new XMLTokener(string)); + } + + /** + * Convert a well-formed (but not necessarily valid) XML string into a JSONArray using the JsonML + * transform. Each XML tag is represented as a JSONArray in which the first element is the tag + * name. If the tag has attributes, then the second element will be JSONObject containing the + * name/value pairs. If the tag contains children, then strings and JSONArrays will represent the + * child content and tags. Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * + * @param x An XMLTokener. + * @return A JSONArray containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONArray toJSONArray(XMLTokener x) throws JSONException { + return (JSONArray) parse(x, true, null); + } + + /** + * Convert a well-formed (but not necessarily valid) XML string into a JSONObject using the JsonML + * transform. Each XML tag is represented as a JSONObject with a "tagName" property. If the tag + * has attributes, then the attributes will be in the JSONObject as properties. If the tag + * contains children, the object will have a "childNodes" property which will be an array of + * strings and JsonML JSONObjects. + * + *

Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * + * @param x An XMLTokener of the XML source text. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONObject toJSONObject(XMLTokener x) throws JSONException { + return (JSONObject) parse(x, false, null); + } + + /** + * Convert a well-formed (but not necessarily valid) XML string into a JSONObject using the JsonML + * transform. Each XML tag is represented as a JSONObject with a "tagName" property. If the tag + * has attributes, then the attributes will be in the JSONObject as properties. If the tag + * contains children, the object will have a "childNodes" property which will be an array of + * strings and JsonML JSONObjects. + * + *

Comments, prologs, DTDs, and <[ [ ]]> are ignored. + * + * @param string The XML source text. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + return toJSONObject(new XMLTokener(string)); + } + + /** + * Reverse the JSONML transformation, making an XML text from a JSONArray. + * + * @param ja A JSONArray. + * @return An XML string. + * @throws JSONException + */ + public static String toString(JSONArray ja) throws JSONException { + int i; + JSONObject jo; + String key; + Iterator keys; + int length; + Object object; + StringBuffer sb = new StringBuffer(); + String tagName; + String value; + + // Emit = length) { + sb.append('/'); + sb.append('>'); + } else { + sb.append('>'); + do { + object = ja.get(i); + i += 1; + if (object != null) { + if (object instanceof String) { + sb.append(XML.escape(object.toString())); + } else if (object instanceof JSONObject) { + sb.append(toString((JSONObject) object)); + } else if (object instanceof JSONArray) { + sb.append(toString((JSONArray) object)); + } } - XML.noSpace(tagName); - tagName = XML.escape(tagName); - sb.append('<'); - sb.append(tagName); - -//Emit the attributes - - keys = jo.keys(); - while (keys.hasNext()) { - key = keys.next().toString(); - if (!key.equals("tagName") && !key.equals("childNodes")) { - XML.noSpace(key); - value = jo.optString(key); - if (value != null) { - sb.append(' '); - sb.append(XML.escape(key)); - sb.append('='); - sb.append('"'); - sb.append(XML.escape(value)); - sb.append('"'); - } - } - } - -//Emit content in body - - ja = jo.optJSONArray("childNodes"); - if (ja == null) { - sb.append('/'); - sb.append('>'); - } else { - sb.append('>'); - length = ja.length(); - for (i = 0; i < length; i += 1) { - object = ja.get(i); - if (object != null) { - if (object instanceof String) { - sb.append(XML.escape(object.toString())); - } else if (object instanceof JSONObject) { - sb.append(toString((JSONObject) object)); - } else if (object instanceof JSONArray) { - sb.append(toString((JSONArray) object)); - } - } - } - sb.append('<'); - sb.append('/'); - sb.append(tagName); - sb.append('>'); - } - return sb.toString(); + } while (i < length); + sb.append('<'); + sb.append('/'); + sb.append(tagName); + sb.append('>'); } -} \ No newline at end of file + return sb.toString(); + } + + /** + * Reverse the JSONML transformation, making an XML text from a JSONObject. The JSONObject must + * contain a "tagName" property. If it has children, then it must have a "childNodes" property + * containing an array of objects. The other properties are attributes with string values. + * + * @param jo A JSONObject. + * @return An XML string. + * @throws JSONException + */ + public static String toString(JSONObject jo) throws JSONException { + StringBuffer sb = new StringBuffer(); + int i; + JSONArray ja; + String key; + Iterator keys; + int length; + Object object; + String tagName; + String value; + + // Emit '); + } else { + sb.append('>'); + length = ja.length(); + for (i = 0; i < length; i += 1) { + object = ja.get(i); + if (object != null) { + if (object instanceof String) { + sb.append(XML.escape(object.toString())); + } else if (object instanceof JSONObject) { + sb.append(toString((JSONObject) object)); + } else if (object instanceof JSONArray) { + sb.append(toString((JSONArray) object)); + } + } + } + sb.append('<'); + sb.append('/'); + sb.append(tagName); + sb.append('>'); + } + return sb.toString(); + } +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONObject.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONObject.java index 3d487d4..bb3cfaa 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONObject.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONObject.java @@ -24,52 +24,46 @@ import java.lang.reflect.Modifier; import java.util.*; /** - * A JSONObject is an unordered collection of name/value pairs. Its - * external form is a string wrapped in curly braces with colons between the - * names and values, and commas between the values and names. The internal form - * is an object having get and opt methods for - * accessing the values by name, and put methods for adding or - * replacing values by name. The values can be any of these types: - * Boolean, JSONArray, JSONObject, - * Number, String, or the JSONObject.NULL - * object. A JSONObject constructor can be used to convert an external form - * JSON text into an internal form whose values can be retrieved with the - * get and opt methods, or to convert values into a - * JSON text using the put and toString methods. - * A get method returns a value if one can be found, and throws an - * exception if one cannot be found. An opt method returns a - * default value instead of throwing an exception, and so is useful for - * obtaining optional values. - *

- * The generic get() and opt() methods return an - * object, which you can cast or query for type. There are also typed - * get and opt methods that do type checking and type - * coercion for you. The opt methods differ from the get methods in that they - * do not throw. Instead, they return a specified value, such as null. - *

- * The put methods add or replace values in an object. For example, + * A JSONObject is an unordered collection of name/value pairs. Its external form is a string + * wrapped in curly braces with colons between the names and values, and commas between the values + * and names. The internal form is an object having get and opt methods + * for accessing the values by name, and put methods for adding or replacing values by + * name. The values can be any of these types: Boolean, JSONArray, + * JSONObject, Number, String, or the JSONObject.NULL + * object. A JSONObject constructor can be used to convert an external form JSON text into an + * internal form whose values can be retrieved with the get and opt + * methods, or to convert values into a JSON text using the put and toString + * methods. A get method returns a value if one can be found, and throws an + * exception if one cannot be found. An opt method returns a default value instead of + * throwing an exception, and so is useful for obtaining optional values. + * + *

The generic get() and opt() methods return an object, which you can + * cast or query for type. There are also typed get and opt methods that + * do type checking and type coercion for you. The opt methods differ from the get methods in that + * they do not throw. Instead, they return a specified value, such as null. + * + *

The put methods add or replace values in an object. For example, + * *

myString = new JSONObject().put("JSON", "Hello, World!").toString();
+ * * produces the string {"JSON": "Hello, World"}. - *

- * The texts produced by the toString methods strictly conform to - * the JSON syntax rules. - * The constructors are more forgiving in the texts they will accept: + * + *

The texts produced by the toString methods strictly conform to the JSON syntax + * rules. The constructors are more forgiving in the texts they will accept: + * *

    - *
  • An extra , (comma) may appear just - * before the closing brace.
  • - *
  • Strings may be quoted with ' (single - * quote).
  • - *
  • Strings do not need to be quoted at all if they do not begin with a quote - * or single quote, and if they do not contain leading or trailing spaces, - * and if they do not contain any of these characters: - * { } [ ] / \ : , = ; # and if they do not look like numbers - * and if they are not the reserved words true, - * false, or null.
  • - *
  • Keys can be followed by = or => as well as - * by :.
  • - *
  • Values can be followed by ; (semicolon) as - * well as by , (comma).
  • - *
  • Numbers may have the 0x- (hex) prefix.
  • + *
  • An extra , (comma) may appear just before the closing + * brace. + *
  • Strings may be quoted with ' (single quote). + *
  • Strings do not need to be quoted at all if they do not begin with a quote or single quote, + * and if they do not contain leading or trailing spaces, and if they do not contain any of + * these characters: { } [ ] / \ : , = ; # and if they do not look like numbers + * and if they are not the reserved words true, false, or null + * . + *
  • Keys can be followed by = or => as well as by :. + *
  • Values can be followed by ; (semicolon) as well as by , + * (comma). + *
  • Numbers may have the 0x- (hex) prefix. *
* * @author JSON.org @@ -77,1524 +71,1423 @@ import java.util.*; */ public class JSONObject { - /** - * It is sometimes more convenient and less ambiguous to have a - * NULL object than to use Java's null value. - * JSONObject.NULL.equals(null) returns true. - * JSONObject.NULL.toString() returns "null". - */ - public static final Object NULL = new Null(); - /** - * The map where the JSONObject's properties are kept. - */ - private Map map; + /** + * It is sometimes more convenient and less ambiguous to have a NULL object than to + * use Java's null value. JSONObject.NULL.equals(null) returns + * true. JSONObject.NULL.toString() returns "null". + */ + public static final Object NULL = new Null(); + /** The map where the JSONObject's properties are kept. */ + private Map map; - /** - * Construct an empty JSONObject. - */ - public JSONObject() { - this.map = new HashMap(); + /** Construct an empty JSONObject. */ + public JSONObject() { + this.map = new HashMap(); + } + + /** + * Construct a JSONObject from a subset of another JSONObject. An array of strings is used to + * identify the keys that should be copied. Missing keys are ignored. + * + * @param jo A JSONObject. + * @param names An array of strings. + * @throws JSONException + * @throws JSONException If a value is a non-finite number or if a name is duplicated. + */ + public JSONObject(JSONObject jo, String[] names) { + this(); + for (int i = 0; i < names.length; i += 1) { + try { + putOnce(names[i], jo.opt(names[i])); + } catch (Exception ignore) { + } + } + } + + /** + * Construct a JSONObject from a JSONTokener. + * + * @param x A JSONTokener object containing the source string. + * @throws JSONException If there is a syntax error in the source string or a duplicated key. + */ + public JSONObject(JSONTokener x) throws JSONException { + this(); + char c; + String key; + + if (x.nextClean() != '{') { + throw x.syntaxError("A JSONObject text must begin with '{'"); + } + for (; ; ) { + c = x.nextClean(); + switch (c) { + case 0: + throw x.syntaxError("A JSONObject text must end with '}'"); + case '}': + return; + default: + x.back(); + key = x.nextValue().toString(); + } + + // The key is followed by ':'. We will also tolerate '=' or '=>'. + + c = x.nextClean(); + if (c == '=') { + if (x.next() != '>') { + x.back(); + } + } else if (c != ':') { + throw x.syntaxError("Expected a ':' after a key"); + } + putOnce(key, x.nextValue()); + + // Pairs are separated by ','. We will also tolerate ';'. + + switch (x.nextClean()) { + case ';': + case ',': + if (x.nextClean() == '}') { + return; + } + x.back(); + break; + case '}': + return; + default: + throw x.syntaxError("Expected a ',' or '}'"); + } + } + } + + /** + * Construct a JSONObject from a Map. + * + * @param map A map object that can be used to initialize the contents of the JSONObject. + * @throws JSONException + */ + public JSONObject(Map map) { + this.map = new HashMap(); + if (map != null) { + Iterator i = map.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry e = (Map.Entry) i.next(); + Object value = e.getValue(); + if (value != null) { + this.map.put(e.getKey(), wrap(value)); + } + } + } + } + + /** + * Construct a JSONObject from an Object using bean getters. It reflects on all of the public + * methods of the object. For each of the methods with no parameters and a name starting with + * "get" or "is" followed by an uppercase letter, the method is invoked, + * and a key and the value returned from the getter method are put into the new JSONObject. + * + *

The key is formed by removing the "get" or "is" prefix. If the + * second remaining character is not upper case, then the first character is converted to lower + * case. + * + *

For example, if an object has a method named "getName", and if the result of + * calling object.getName() is "Larry Fine", then the JSONObject will + * contain "name": "Larry Fine". + * + * @param bean An object that has getter methods that should be used to make a JSONObject. + */ + public JSONObject(Object bean) { + this(); + populateMap(bean); + } + + /** + * Construct a JSONObject from an Object, using reflection to find the public members. The + * resulting JSONObject's keys will be the strings from the names array, and the values will be + * the field values associated with those keys in the object. If a key is not found or not + * visible, then it will not be copied into the new JSONObject. + * + * @param object An object that has fields that should be used to make a JSONObject. + * @param names An array of strings, the names of the fields to be obtained from the object. + */ + public JSONObject(Object object, String names[]) { + this(); + Class c = object.getClass(); + for (int i = 0; i < names.length; i += 1) { + String name = names[i]; + try { + putOpt(name, c.getField(name).get(object)); + } catch (Exception ignore) { + } + } + } + + /** + * Construct a JSONObject from a source JSON text string. This is the most commonly used + * JSONObject constructor. + * + * @param source A string beginning with { (left brace) and + * ending with } (right brace). + * @throws JSONException If there is a syntax error in the source string or a duplicated key. + */ + public JSONObject(String source) throws JSONException { + this(new JSONTokener(source)); + } + + /** + * Construct a JSONObject from a ResourceBundle. + * + * @param baseName The ResourceBundle base name. + * @param locale The Locale to load the ResourceBundle for. + * @throws JSONException If any JSONExceptions are detected. + */ + public JSONObject(String baseName, Locale locale) throws JSONException { + this(); + ResourceBundle bundle = + ResourceBundle.getBundle(baseName, locale, Thread.currentThread().getContextClassLoader()); + + // Iterate through the keys in the bundle. + + Enumeration keys = bundle.getKeys(); + while (keys.hasMoreElements()) { + Object key = keys.nextElement(); + if (key instanceof String) { + + // Go through the path, ensuring that there is a nested JSONObject for each + // segment except the last. Add the value using the last segment's name into + // the deepest nested JSONObject. + + String[] path = ((String) key).split("\\."); + int last = path.length - 1; + JSONObject target = this; + for (int i = 0; i < last; i += 1) { + String segment = path[i]; + JSONObject nextTarget = target.optJSONObject(segment); + if (nextTarget == null) { + nextTarget = new JSONObject(); + target.put(segment, nextTarget); + } + target = nextTarget; + } + target.put(path[last], bundle.getString((String) key)); + } + } + } + + /** + * Produce a string from a double. The string "null" will be returned if the number is not finite. + * + * @param d A double. + * @return A String. + */ + public static String doubleToString(double d) { + if (Double.isInfinite(d) || Double.isNaN(d)) { + return "null"; } + // Shave off trailing zeros and decimal point, if possible. - /** - * Construct a JSONObject from a subset of another JSONObject. - * An array of strings is used to identify the keys that should be copied. - * Missing keys are ignored. - * - * @param jo A JSONObject. - * @param names An array of strings. - * @throws JSONException - * @throws JSONException If a value is a non-finite number or if a name is duplicated. - */ - public JSONObject(JSONObject jo, String[] names) { - this(); - for (int i = 0; i < names.length; i += 1) { - try { - putOnce(names[i], jo.opt(names[i])); - } catch (Exception ignore) { - } - } + String string = Double.toString(d); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Get an array of field names from a JSONObject. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(JSONObject jo) { + int length = jo.length(); + if (length == 0) { + return null; + } + Iterator iterator = jo.keys(); + String[] names = new String[length]; + int i = 0; + while (iterator.hasNext()) { + names[i] = (String) iterator.next(); + i += 1; + } + return names; + } + + /** + * Get an array of field names from an Object. + * + * @return An array of field names, or null if there are no names. + */ + public static String[] getNames(Object object) { + if (object == null) { + return null; + } + Class klass = object.getClass(); + Field[] fields = klass.getFields(); + int length = fields.length; + if (length == 0) { + return null; + } + String[] names = new String[length]; + for (int i = 0; i < length; i += 1) { + names[i] = fields[i].getName(); + } + return names; + } + + /** + * Produce a string from a Number. + * + * @param number A Number + * @return A String. + * @throws JSONException If n is a non-finite number. + */ + public static String numberToString(Number number) throws JSONException { + if (number == null) { + throw new JSONException("Null pointer"); + } + testValidity(number); + + // Shave off trailing zeros and decimal point, if possible. + + String string = number.toString(); + if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && string.indexOf('E') < 0) { + while (string.endsWith("0")) { + string = string.substring(0, string.length() - 1); + } + if (string.endsWith(".")) { + string = string.substring(0, string.length() - 1); + } + } + return string; + } + + /** + * Produce a string in double quotes with backslash sequences in all the right places. A backslash + * will be inserted within = '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) { + hhhh = "000" + Integer.toHexString(c); + sb.append("\\u" + hhhh.substring(hhhh.length() - 4)); + } else { + sb.append(c); + } + } + } + sb.append('"'); + return sb.toString(); + } - if (x.nextClean() != '{') { - throw x.syntaxError("A JSONObject text must begin with '{'"); - } - for (; ; ) { - c = x.nextClean(); - switch (c) { - case 0: - throw x.syntaxError("A JSONObject text must end with '}'"); - case '}': - return; - default: - x.back(); - key = x.nextValue().toString(); - } - -// The key is followed by ':'. We will also tolerate '=' or '=>'. - - c = x.nextClean(); - if (c == '=') { - if (x.next() != '>') { - x.back(); - } - } else if (c != ':') { - throw x.syntaxError("Expected a ':' after a key"); - } - putOnce(key, x.nextValue()); - -// Pairs are separated by ','. We will also tolerate ';'. - - switch (x.nextClean()) { - case ';': - case ',': - if (x.nextClean() == '}') { - return; - } - x.back(); - break; - case '}': - return; - default: - throw x.syntaxError("Expected a ',' or '}'"); - } - } + /** + * Try to convert a string into a number, boolean, or null. If the string can't be converted, + * return the string. + * + * @param string A String. + * @return A simple JSON value. + */ + public static Object stringToValue(String string) { + if (string.isEmpty()) { + return string; + } + if (string.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (string.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + if (string.equalsIgnoreCase("null")) { + return JSONObject.NULL; } - - /** - * Construct a JSONObject from a Map. - * - * @param map A map object that can be used to initialize the contents of - * the JSONObject. - * @throws JSONException + /* + * If it might be a number, try converting it. + * We support the non-standard 0x- convention. + * If a number cannot be produced, then the value will just + * be a string. Note that the 0x-, plus, and implied string + * conventions are non-standard. A JSON parser may accept + * non-JSON forms as long as it accepts all correct JSON forms. */ - public JSONObject(Map map) { - this.map = new HashMap(); - if (map != null) { - Iterator i = map.entrySet().iterator(); - while (i.hasNext()) { - Map.Entry e = (Map.Entry) i.next(); - Object value = e.getValue(); - if (value != null) { - this.map.put(e.getKey(), wrap(value)); - } - } - } - } - - /** - * Construct a JSONObject from an Object using bean getters. - * It reflects on all of the public methods of the object. - * For each of the methods with no parameters and a name starting - * with "get" or "is" followed by an uppercase letter, - * the method is invoked, and a key and the value returned from the getter method - * are put into the new JSONObject. - *

- * The key is formed by removing the "get" or "is" prefix. - * If the second remaining character is not upper case, then the first - * character is converted to lower case. - *

- * For example, if an object has a method named "getName", and - * if the result of calling object.getName() is "Larry Fine", - * then the JSONObject will contain "name": "Larry Fine". - * - * @param bean An object that has getter methods that should be used - * to make a JSONObject. - */ - public JSONObject(Object bean) { - this(); - populateMap(bean); - } - - - /** - * Construct a JSONObject from an Object, using reflection to find the - * public members. The resulting JSONObject's keys will be the strings - * from the names array, and the values will be the field values associated - * with those keys in the object. If a key is not found or not visible, - * then it will not be copied into the new JSONObject. - * - * @param object An object that has fields that should be used to make a - * JSONObject. - * @param names An array of strings, the names of the fields to be obtained - * from the object. - */ - public JSONObject(Object object, String names[]) { - this(); - Class c = object.getClass(); - for (int i = 0; i < names.length; i += 1) { - String name = names[i]; - try { - putOpt(name, c.getField(name).get(object)); - } catch (Exception ignore) { - } - } - } - - - /** - * Construct a JSONObject from a source JSON text string. - * This is the most commonly used JSONObject constructor. - * - * @param source A string beginning - * with { (left brace) and ending - * with } (right brace). - * @throws JSONException If there is a syntax error in the source - * string or a duplicated key. - */ - public JSONObject(String source) throws JSONException { - this(new JSONTokener(source)); - } - - - /** - * Construct a JSONObject from a ResourceBundle. - * - * @param baseName The ResourceBundle base name. - * @param locale The Locale to load the ResourceBundle for. - * @throws JSONException If any JSONExceptions are detected. - */ - public JSONObject(String baseName, Locale locale) throws JSONException { - this(); - ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale, - Thread.currentThread().getContextClassLoader()); - -// Iterate through the keys in the bundle. - - Enumeration keys = bundle.getKeys(); - while (keys.hasMoreElements()) { - Object key = keys.nextElement(); - if (key instanceof String) { - -// Go through the path, ensuring that there is a nested JSONObject for each -// segment except the last. Add the value using the last segment's name into -// the deepest nested JSONObject. - - String[] path = ((String) key).split("\\."); - int last = path.length - 1; - JSONObject target = this; - for (int i = 0; i < last; i += 1) { - String segment = path[i]; - JSONObject nextTarget = target.optJSONObject(segment); - if (nextTarget == null) { - nextTarget = new JSONObject(); - target.put(segment, nextTarget); - } - target = nextTarget; - } - target.put(path[last], bundle.getString((String) key)); - } - } - } - - /** - * Produce a string from a double. The string "null" will be returned if - * the number is not finite. - * - * @param d A double. - * @return A String. - */ - public static String doubleToString(double d) { - if (Double.isInfinite(d) || Double.isNaN(d)) { - return "null"; - } - -// Shave off trailing zeros and decimal point, if possible. - - String string = Double.toString(d); - if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && - string.indexOf('E') < 0) { - while (string.endsWith("0")) { - string = string.substring(0, string.length() - 1); - } - if (string.endsWith(".")) { - string = string.substring(0, string.length() - 1); - } - } - return string; - } - - /** - * Get an array of field names from a JSONObject. - * - * @return An array of field names, or null if there are no names. - */ - public static String[] getNames(JSONObject jo) { - int length = jo.length(); - if (length == 0) { - return null; - } - Iterator iterator = jo.keys(); - String[] names = new String[length]; - int i = 0; - while (iterator.hasNext()) { - names[i] = (String) iterator.next(); - i += 1; - } - return names; - } - - /** - * Get an array of field names from an Object. - * - * @return An array of field names, or null if there are no names. - */ - public static String[] getNames(Object object) { - if (object == null) { - return null; - } - Class klass = object.getClass(); - Field[] fields = klass.getFields(); - int length = fields.length; - if (length == 0) { - return null; - } - String[] names = new String[length]; - for (int i = 0; i < length; i += 1) { - names[i] = fields[i].getName(); - } - return names; - } - - /** - * Produce a string from a Number. - * - * @param number A Number - * @return A String. - * @throws JSONException If n is a non-finite number. - */ - public static String numberToString(Number number) - throws JSONException { - if (number == null) { - throw new JSONException("Null pointer"); - } - testValidity(number); - -// Shave off trailing zeros and decimal point, if possible. - - String string = number.toString(); - if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && - string.indexOf('E') < 0) { - while (string.endsWith("0")) { - string = string.substring(0, string.length() - 1); - } - if (string.endsWith(".")) { - string = string.substring(0, string.length() - 1); - } - } - return string; - } - - /** - * Produce a string in double quotes with backslash sequences in all the - * right places. A backslash will be inserted within = '\u0080' && c < '\u00a0') || - (c >= '\u2000' && c < '\u2100')) { - hhhh = "000" + Integer.toHexString(c); - sb.append("\\u" + hhhh.substring(hhhh.length() - 4)); - } else { - sb.append(c); - } - } - } - sb.append('"'); - return sb.toString(); - } - - /** - * Try to convert a string into a number, boolean, or null. If the string - * can't be converted, return the string. - * - * @param string A String. - * @return A simple JSON value. - */ - public static Object stringToValue(String string) { - if (string.equals("")) { - return string; - } - if (string.equalsIgnoreCase("true")) { - return Boolean.TRUE; - } - if (string.equalsIgnoreCase("false")) { - return Boolean.FALSE; - } - if (string.equalsIgnoreCase("null")) { - return JSONObject.NULL; - } - - /* - * If it might be a number, try converting it. - * We support the non-standard 0x- convention. - * If a number cannot be produced, then the value will just - * be a string. Note that the 0x-, plus, and implied string - * conventions are non-standard. A JSON parser may accept - * non-JSON forms as long as it accepts all correct JSON forms. - */ - - char b = string.charAt(0); - if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') { - if (b == '0' && string.length() > 2 && - (string.charAt(1) == 'x' || string.charAt(1) == 'X')) { - try { - return new Integer(Integer.parseInt(string.substring(2), 16)); - } catch (Exception ignore) { - } - } - try { - if (string.indexOf('.') > -1 || - string.indexOf('e') > -1 || string.indexOf('E') > -1) { - return Double.valueOf(string); - } else { - Long myLong = new Long(string); - if (myLong.longValue() == myLong.intValue()) { - return new Integer(myLong.intValue()); - } else { - return myLong; - } - } - } catch (Exception ignore) { - } - } - return string; - } - - /** - * Throw an exception if the object is a NaN or infinite number. - * - * @param o The object to test. - * @throws JSONException If o is a non-finite number. - */ - public static void testValidity(Object o) throws JSONException { - if (o != null) { - if (o instanceof Double) { - if (((Double) o).isInfinite() || ((Double) o).isNaN()) { - throw new JSONException( - "JSON does not allow non-finite numbers."); - } - } else if (o instanceof Float) { - if (((Float) o).isInfinite() || ((Float) o).isNaN()) { - throw new JSONException( - "JSON does not allow non-finite numbers."); - } - } - } - } - - /** - * Make a JSON text of an Object value. If the object has an - * value.toJSONString() method, then that method will be used to produce - * the JSON text. The method is required to produce a strictly - * conforming text. If the object does not contain a toJSONString - * method (which is the most common case), then a text will be - * produced by other means. If the value is an array or Collection, - * then a JSONArray will be made from it and its toJSONString method - * will be called. If the value is a MAP, then a JSONObject will be made - * from it and its toJSONString method will be called. Otherwise, the - * value's toString method will be called, and the result will be quoted. - * - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @param value The value to be serialized. - * @return a printable, displayable, transmittable - * representation of the object, beginning - * with { (left brace) and ending - * with } (right brace). - * @throws JSONException If the value is or contains an invalid number. - */ - public static String valueToString(Object value) throws JSONException { - if (value == null || value.equals(null)) { - return "null"; - } - if (value instanceof JSONString) { - Object object; - try { - object = ((JSONString) value).toJSONString(); - } catch (Exception e) { - throw new JSONException(e); - } - if (object instanceof String) { - return (String) object; - } - throw new JSONException("Bad value from toJSONString: " + object); - } - if (value instanceof Number) { - return numberToString((Number) value); - } - if (value instanceof Boolean || value instanceof JSONObject || - value instanceof JSONArray) { - return value.toString(); - } - if (value instanceof Map) { - return new JSONObject((Map) value).toString(); - } - if (value instanceof Collection) { - return new JSONArray((Collection) value).toString(); - } - if (value.getClass().isArray()) { - return new JSONArray(value).toString(); - } - return quote(value.toString()); - } - - /** - * Make a prettyprinted JSON text of an object value. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @param value The value to be serialized. - * @param indentFactor The number of spaces to add to each level of - * indentation. - * @param indent The indentation of the top level. - * @return a printable, displayable, transmittable - * representation of the object, beginning - * with { (left brace) and ending - * with } (right brace). - * @throws JSONException If the object contains an invalid number. - */ - static String valueToString( - Object value, - int indentFactor, - int indent - ) throws JSONException { - if (value == null || value.equals(null)) { - return "null"; - } + char b = string.charAt(0); + if ((b >= '0' && b <= '9') || b == '.' || b == '-' || b == '+') { + if (b == '0' && string.length() > 2 && (string.charAt(1) == 'x' || string.charAt(1) == 'X')) { try { - if (value instanceof JSONString) { - Object o = ((JSONString) value).toJSONString(); - if (o instanceof String) { - return (String) o; - } - } + return new Integer(Integer.parseInt(string.substring(2), 16)); } catch (Exception ignore) { } - if (value instanceof Number) { - return numberToString((Number) value); - } - if (value instanceof Boolean) { - return value.toString(); - } - if (value instanceof JSONObject) { - return ((JSONObject) value).toString(indentFactor, indent); - } - if (value instanceof JSONArray) { - return ((JSONArray) value).toString(indentFactor, indent); - } - if (value instanceof Map) { - return new JSONObject((Map) value).toString(indentFactor, indent); - } - if (value instanceof Collection) { - return new JSONArray((Collection) value).toString(indentFactor, indent); - } - if (value.getClass().isArray()) { - return new JSONArray(value).toString(indentFactor, indent); - } - return quote(value.toString()); - } - - /** - * Wrap an object, if necessary. If the object is null, return the NULL - * object. If it is an array or collection, wrap it in a JSONArray. If - * it is a map, wrap it in a JSONObject. If it is a standard property - * (Double, String, et al) then it is already wrapped. Otherwise, if it - * comes from one of the java packages, turn it into a string. And if - * it doesn't, try to wrap it in a JSONObject. If the wrapping fails, - * then null is returned. - * - * @param object The object to wrap - * @return The wrapped value - */ - public static Object wrap(Object object) { - try { - if (object == null) { - return NULL; - } - if (object instanceof JSONObject || object instanceof JSONArray || - NULL.equals(object) || object instanceof JSONString || - object instanceof Byte || object instanceof Character || - object instanceof Short || object instanceof Integer || - object instanceof Long || object instanceof Boolean || - object instanceof Float || object instanceof Double || - object instanceof String) { - return object; - } - - if (object instanceof Collection) { - return new JSONArray((Collection) object); - } - if (object.getClass().isArray()) { - return new JSONArray(object); - } - if (object instanceof Map) { - return new JSONObject((Map) object); - } - Package objectPackage = object.getClass().getPackage(); - String objectPackageName = objectPackage != null ? - objectPackage.getName() : ""; - if ( - objectPackageName.startsWith("java.") || - objectPackageName.startsWith("javax.") || - object.getClass().getClassLoader() == null - ) { - return object.toString(); - } - return new JSONObject(object); - } catch (Exception exception) { - return null; - } - } - - /** - * Accumulate values under a key. It is similar to the put method except - * that if there is already an object stored under the key then a - * JSONArray is stored under the key to hold all of the accumulated values. - * If there is already a JSONArray, then the new value is appended to it. - * In contrast, the put method replaces the previous value. - *

- * If only one value is accumulated that is not a JSONArray, then the - * result will be the same as using put. But if multiple values are - * accumulated, then the result will be like append. - * - * @param key A key string. - * @param value An object to be accumulated under the key. - * @return this. - * @throws JSONException If the value is an invalid number - * or if the key is null. - */ - public JSONObject accumulate( - String key, - Object value - ) throws JSONException { - testValidity(value); - Object object = opt(key); - if (object == null) { - put(key, value instanceof JSONArray ? - new JSONArray().put(value) : value); - } else if (object instanceof JSONArray) { - ((JSONArray) object).put(value); + } + try { + if (string.indexOf('.') > -1 || string.indexOf('e') > -1 || string.indexOf('E') > -1) { + return Double.valueOf(string); } else { - put(key, new JSONArray().put(object).put(value)); + Long myLong = new Long(string); + if (myLong.longValue() == myLong.intValue()) { + return new Integer(myLong.intValue()); + } else { + return myLong; + } } - return this; + } catch (Exception ignore) { + } } + return string; + } - /** - * Append values to the array under a key. If the key does not exist in the - * JSONObject, then the key is put in the JSONObject with its value being a - * JSONArray containing the value parameter. If the key was already - * associated with a JSONArray, then the value parameter is appended to it. - * - * @param key A key string. - * @param value An object to be accumulated under the key. - * @return this. - * @throws JSONException If the key is null or if the current value - * associated with the key is not a JSONArray. - */ - public JSONObject append(String key, Object value) throws JSONException { - testValidity(value); - Object object = opt(key); - if (object == null) { - put(key, new JSONArray().put(value)); - } else if (object instanceof JSONArray) { - put(key, ((JSONArray) object).put(value)); - } else { - throw new JSONException("JSONObject[" + key + - "] is not a JSONArray."); + /** + * Throw an exception if the object is a NaN or infinite number. + * + * @param o The object to test. + * @throws JSONException If o is a non-finite number. + */ + public static void testValidity(Object o) throws JSONException { + if (o != null) { + if (o instanceof Double) { + if (((Double) o).isInfinite() || ((Double) o).isNaN()) { + throw new JSONException("JSON does not allow non-finite numbers."); } - return this; + } else if (o instanceof Float) { + if (((Float) o).isInfinite() || ((Float) o).isNaN()) { + throw new JSONException("JSON does not allow non-finite numbers."); + } + } } + } - /** - * Get the value object associated with a key. - * - * @param key A key string. - * @return The object associated with the key. - * @throws JSONException if the key is not found. - */ - public Object get(String key) throws JSONException { - if (key == null) { - throw new JSONException("Null key."); - } - Object object = opt(key); - if (object == null) { - throw new JSONException("JSONObject[" + quote(key) + - "] not found."); + /** + * Make a JSON text of an Object value. If the object has an value.toJSONString() method, then + * that method will be used to produce the JSON text. The method is required to produce a strictly + * conforming text. If the object does not contain a toJSONString method (which is the most common + * case), then a text will be produced by other means. If the value is an array or Collection, + * then a JSONArray will be made from it and its toJSONString method will be called. If the value + * is a MAP, then a JSONObject will be made from it and its toJSONString method will be called. + * Otherwise, the value's toString method will be called, and the result will be quoted. + * + *

Warning: This method assumes that the data structure is acyclical. + * + * @param value The value to be serialized. + * @return a printable, displayable, transmittable representation of the object, beginning with + * { (left brace) and ending with } + *  (right brace). + * @throws JSONException If the value is or contains an invalid number. + */ + public static String valueToString(Object value) throws JSONException { + if (value == null || value.equals(null)) { + return "null"; + } + if (value instanceof JSONString) { + Object object; + try { + object = ((JSONString) value).toJSONString(); + } catch (Exception e) { + throw new JSONException(e); + } + if (object instanceof String) { + return (String) object; + } + throw new JSONException("Bad value from toJSONString: " + object); + } + if (value instanceof Number) { + return numberToString((Number) value); + } + if (value instanceof Boolean || value instanceof JSONObject || value instanceof JSONArray) { + return value.toString(); + } + if (value instanceof Map) { + return new JSONObject((Map) value).toString(); + } + if (value instanceof Collection) { + return new JSONArray((Collection) value).toString(); + } + if (value.getClass().isArray()) { + return new JSONArray(value).toString(); + } + return quote(value.toString()); + } + + /** + * Make a prettyprinted JSON text of an object value. + * + *

Warning: This method assumes that the data structure is acyclical. + * + * @param value The value to be serialized. + * @param indentFactor The number of spaces to add to each level of indentation. + * @param indent The indentation of the top level. + * @return a printable, displayable, transmittable representation of the object, beginning with + * { (left brace) and ending with } + *  (right brace). + * @throws JSONException If the object contains an invalid number. + */ + static String valueToString(Object value, int indentFactor, int indent) throws JSONException { + if (value == null || value.equals(null)) { + return "null"; + } + try { + if (value instanceof JSONString) { + Object o = ((JSONString) value).toJSONString(); + if (o instanceof String) { + return (String) o; } + } + } catch (Exception ignore) { + } + if (value instanceof Number) { + return numberToString((Number) value); + } + if (value instanceof Boolean) { + return value.toString(); + } + if (value instanceof JSONObject) { + return ((JSONObject) value).toString(indentFactor, indent); + } + if (value instanceof JSONArray) { + return ((JSONArray) value).toString(indentFactor, indent); + } + if (value instanceof Map) { + return new JSONObject((Map) value).toString(indentFactor, indent); + } + if (value instanceof Collection) { + return new JSONArray((Collection) value).toString(indentFactor, indent); + } + if (value.getClass().isArray()) { + return new JSONArray(value).toString(indentFactor, indent); + } + return quote(value.toString()); + } + + /** + * Wrap an object, if necessary. If the object is null, return the NULL object. If it is an array + * or collection, wrap it in a JSONArray. If it is a map, wrap it in a JSONObject. If it is a + * standard property (Double, String, et al) then it is already wrapped. Otherwise, if it comes + * from one of the java packages, turn it into a string. And if it doesn't, try to wrap it in a + * JSONObject. If the wrapping fails, then null is returned. + * + * @param object The object to wrap + * @return The wrapped value + */ + public static Object wrap(Object object) { + try { + if (object == null) { + return NULL; + } + if (object instanceof JSONObject + || object instanceof JSONArray + || NULL.equals(object) + || object instanceof JSONString + || object instanceof Byte + || object instanceof Character + || object instanceof Short + || object instanceof Integer + || object instanceof Long + || object instanceof Boolean + || object instanceof Float + || object instanceof Double + || object instanceof String) { return object; + } + + if (object instanceof Collection) { + return new JSONArray((Collection) object); + } + if (object.getClass().isArray()) { + return new JSONArray(object); + } + if (object instanceof Map) { + return new JSONObject((Map) object); + } + Package objectPackage = object.getClass().getPackage(); + String objectPackageName = objectPackage != null ? objectPackage.getName() : ""; + if (objectPackageName.startsWith("java.") + || objectPackageName.startsWith("javax.") + || object.getClass().getClassLoader() == null) { + return object.toString(); + } + return new JSONObject(object); + } catch (Exception exception) { + return null; } + } - /** - * Get the boolean value associated with a key. - * - * @param key A key string. - * @return The truth. - * @throws JSONException if the value is not a Boolean or the String "true" or "false". - */ - public boolean getBoolean(String key) throws JSONException { - Object object = get(key); - if (object.equals(Boolean.FALSE) || - (object instanceof String && - ((String) object).equalsIgnoreCase("false"))) { - return false; - } else if (object.equals(Boolean.TRUE) || - (object instanceof String && - ((String) object).equalsIgnoreCase("true"))) { - return true; - } - throw new JSONException("JSONObject[" + quote(key) + - "] is not a Boolean."); + /** + * Accumulate values under a key. It is similar to the put method except that if there is already + * an object stored under the key then a JSONArray is stored under the key to hold all of the + * accumulated values. If there is already a JSONArray, then the new value is appended to it. In + * contrast, the put method replaces the previous value. + * + *

If only one value is accumulated that is not a JSONArray, then the result will be the same + * as using put. But if multiple values are accumulated, then the result will be like append. + * + * @param key A key string. + * @param value An object to be accumulated under the key. + * @return this. + * @throws JSONException If the value is an invalid number or if the key is null. + */ + public JSONObject accumulate(String key, Object value) throws JSONException { + testValidity(value); + Object object = opt(key); + if (object == null) { + put(key, value instanceof JSONArray ? new JSONArray().put(value) : value); + } else if (object instanceof JSONArray) { + ((JSONArray) object).put(value); + } else { + put(key, new JSONArray().put(object).put(value)); } + return this; + } - /** - * Get the double value associated with a key. - * - * @param key A key string. - * @return The numeric value. - * @throws JSONException if the key is not found or - * if the value is not a Number object and cannot be converted to a number. - */ - public double getDouble(String key) throws JSONException { - Object object = get(key); - try { - return object instanceof Number ? - ((Number) object).doubleValue() : - Double.parseDouble((String) object); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) + - "] is not a number."); - } + /** + * Append values to the array under a key. If the key does not exist in the JSONObject, then the + * key is put in the JSONObject with its value being a JSONArray containing the value parameter. + * If the key was already associated with a JSONArray, then the value parameter is appended to it. + * + * @param key A key string. + * @param value An object to be accumulated under the key. + * @return this. + * @throws JSONException If the key is null or if the current value associated with the key is not + * a JSONArray. + */ + public JSONObject append(String key, Object value) throws JSONException { + testValidity(value); + Object object = opt(key); + if (object == null) { + put(key, new JSONArray().put(value)); + } else if (object instanceof JSONArray) { + put(key, ((JSONArray) object).put(value)); + } else { + throw new JSONException("JSONObject[" + key + "] is not a JSONArray."); } + return this; + } - /** - * Get the int value associated with a key. - * - * @param key A key string. - * @return The integer value. - * @throws JSONException if the key is not found or if the value cannot - * be converted to an integer. - */ - public int getInt(String key) throws JSONException { - Object object = get(key); - try { - return object instanceof Number ? - ((Number) object).intValue() : - Integer.parseInt((String) object); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) + - "] is not an int."); - } + /** + * Get the value object associated with a key. + * + * @param key A key string. + * @return The object associated with the key. + * @throws JSONException if the key is not found. + */ + public Object get(String key) throws JSONException { + if (key == null) { + throw new JSONException("Null key."); } - - /** - * Get the JSONArray value associated with a key. - * - * @param key A key string. - * @return A JSONArray which is the value. - * @throws JSONException if the key is not found or - * if the value is not a JSONArray. - */ - public JSONArray getJSONArray(String key) throws JSONException { - Object object = get(key); - if (object instanceof JSONArray) { - return (JSONArray) object; - } - throw new JSONException("JSONObject[" + quote(key) + - "] is not a JSONArray."); + Object object = opt(key); + if (object == null) { + throw new JSONException("JSONObject[" + quote(key) + "] not found."); } + return object; + } - /** - * Get the JSONObject value associated with a key. - * - * @param key A key string. - * @return A JSONObject which is the value. - * @throws JSONException if the key is not found or - * if the value is not a JSONObject. - */ - public JSONObject getJSONObject(String key) throws JSONException { - Object object = get(key); - if (object instanceof JSONObject) { - return (JSONObject) object; - } - throw new JSONException("JSONObject[" + quote(key) + - "] is not a JSONObject."); + /** + * Get the boolean value associated with a key. + * + * @param key A key string. + * @return The truth. + * @throws JSONException if the value is not a Boolean or the String "true" or "false". + */ + public boolean getBoolean(String key) throws JSONException { + Object object = get(key); + if (object.equals(Boolean.FALSE) + || (object instanceof String && ((String) object).equalsIgnoreCase("false"))) { + return false; + } else if (object.equals(Boolean.TRUE) + || (object instanceof String && ((String) object).equalsIgnoreCase("true"))) { + return true; } + throw new JSONException("JSONObject[" + quote(key) + "] is not a Boolean."); + } - /** - * Get the long value associated with a key. - * - * @param key A key string. - * @return The long value. - * @throws JSONException if the key is not found or if the value cannot - * be converted to a long. - */ - public long getLong(String key) throws JSONException { - Object object = get(key); - try { - return object instanceof Number ? - ((Number) object).longValue() : - Long.parseLong((String) object); - } catch (Exception e) { - throw new JSONException("JSONObject[" + quote(key) + - "] is not a long."); - } + /** + * Get the double value associated with a key. + * + * @param key A key string. + * @return The numeric value. + * @throws JSONException if the key is not found or if the value is not a Number object and cannot + * be converted to a number. + */ + public double getDouble(String key) throws JSONException { + Object object = get(key); + try { + return object instanceof Number + ? ((Number) object).doubleValue() + : Double.parseDouble((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not a number."); } + } - /** - * Get the string associated with a key. - * - * @param key A key string. - * @return A string which is the value. - * @throws JSONException if there is no string value for the key. - */ - public String getString(String key) throws JSONException { - Object object = get(key); - if (object instanceof String) { - return (String) object; - } - throw new JSONException("JSONObject[" + quote(key) + - "] not a string."); + /** + * Get the int value associated with a key. + * + * @param key A key string. + * @return The integer value. + * @throws JSONException if the key is not found or if the value cannot be converted to an + * integer. + */ + public int getInt(String key) throws JSONException { + Object object = get(key); + try { + return object instanceof Number + ? ((Number) object).intValue() + : Integer.parseInt((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not an int."); } + } - /** - * Determine if the JSONObject contains a specific key. - * - * @param key A key string. - * @return true if the key exists in the JSONObject. - */ - public boolean has(String key) { - return this.map.containsKey(key); + /** + * Get the JSONArray value associated with a key. + * + * @param key A key string. + * @return A JSONArray which is the value. + * @throws JSONException if the key is not found or if the value is not a JSONArray. + */ + public JSONArray getJSONArray(String key) throws JSONException { + Object object = get(key); + if (object instanceof JSONArray) { + return (JSONArray) object; } + throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONArray."); + } - /** - * Increment a property of a JSONObject. If there is no such property, - * create one with a value of 1. If there is such a property, and if - * it is an Integer, Long, Double, or Float, then add one to it. - * - * @param key A key string. - * @return this. - * @throws JSONException If there is already a property with this name - * that is not an Integer, Long, Double, or Float. - */ - public JSONObject increment(String key) throws JSONException { - Object value = opt(key); - if (value == null) { - put(key, 1); - } else if (value instanceof Integer) { - put(key, ((Integer) value).intValue() + 1); - } else if (value instanceof Long) { - put(key, ((Long) value).longValue() + 1); - } else if (value instanceof Double) { - put(key, ((Double) value).doubleValue() + 1); - } else if (value instanceof Float) { - put(key, ((Float) value).floatValue() + 1); - } else { - throw new JSONException("Unable to increment [" + quote(key) + "]."); - } - return this; + /** + * Get the JSONObject value associated with a key. + * + * @param key A key string. + * @return A JSONObject which is the value. + * @throws JSONException if the key is not found or if the value is not a JSONObject. + */ + public JSONObject getJSONObject(String key) throws JSONException { + Object object = get(key); + if (object instanceof JSONObject) { + return (JSONObject) object; } + throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONObject."); + } - /** - * Determine if the value associated with the key is null or if there is - * no value. - * - * @param key A key string. - * @return true if there is no value associated with the key or if - * the value is the JSONObject.NULL object. - */ - public boolean isNull(String key) { - return JSONObject.NULL.equals(opt(key)); + /** + * Get the long value associated with a key. + * + * @param key A key string. + * @return The long value. + * @throws JSONException if the key is not found or if the value cannot be converted to a long. + */ + public long getLong(String key) throws JSONException { + Object object = get(key); + try { + return object instanceof Number + ? ((Number) object).longValue() + : Long.parseLong((String) object); + } catch (Exception e) { + throw new JSONException("JSONObject[" + quote(key) + "] is not a long."); } + } - /** - * Get an enumeration of the keys of the JSONObject. - * - * @return An iterator of the keys. - */ - public Iterator keys() { - return this.map.keySet().iterator(); + /** + * Get the string associated with a key. + * + * @param key A key string. + * @return A string which is the value. + * @throws JSONException if there is no string value for the key. + */ + public String getString(String key) throws JSONException { + Object object = get(key); + if (object instanceof String) { + return (String) object; } + throw new JSONException("JSONObject[" + quote(key) + "] not a string."); + } - public Set keySet() { - return this.map.keySet(); + /** + * Determine if the JSONObject contains a specific key. + * + * @param key A key string. + * @return true if the key exists in the JSONObject. + */ + public boolean has(String key) { + return this.map.containsKey(key); + } + + /** + * Increment a property of a JSONObject. If there is no such property, create one with a value of + * 1. If there is such a property, and if it is an Integer, Long, Double, or Float, then add one + * to it. + * + * @param key A key string. + * @return this. + * @throws JSONException If there is already a property with this name that is not an Integer, + * Long, Double, or Float. + */ + public JSONObject increment(String key) throws JSONException { + Object value = opt(key); + if (value == null) { + put(key, 1); + } else if (value instanceof Integer) { + put(key, ((Integer) value).intValue() + 1); + } else if (value instanceof Long) { + put(key, ((Long) value).longValue() + 1); + } else if (value instanceof Double) { + put(key, ((Double) value).doubleValue() + 1); + } else if (value instanceof Float) { + put(key, ((Float) value).floatValue() + 1); + } else { + throw new JSONException("Unable to increment [" + quote(key) + "]."); } + return this; + } - /** - * Get the number of keys stored in the JSONObject. - * - * @return The number of keys in the JSONObject. - */ - public int length() { - return this.map.size(); + /** + * Determine if the value associated with the key is null or if there is no value. + * + * @param key A key string. + * @return true if there is no value associated with the key or if the value is the + * JSONObject.NULL object. + */ + public boolean isNull(String key) { + return JSONObject.NULL.equals(opt(key)); + } + + /** + * Get an enumeration of the keys of the JSONObject. + * + * @return An iterator of the keys. + */ + public Iterator keys() { + return this.map.keySet().iterator(); + } + + public Set keySet() { + return this.map.keySet(); + } + + /** + * Get the number of keys stored in the JSONObject. + * + * @return The number of keys in the JSONObject. + */ + public int length() { + return this.map.size(); + } + + /** + * Produce a JSONArray containing the names of the elements of this JSONObject. + * + * @return A JSONArray containing the key strings, or null if the JSONObject is empty. + */ + public JSONArray names() { + JSONArray ja = new JSONArray(); + Iterator keys = this.keys(); + while (keys.hasNext()) { + ja.put(keys.next()); } + return ja.length() == 0 ? null : ja; + } - /** - * Produce a JSONArray containing the names of the elements of this - * JSONObject. - * - * @return A JSONArray containing the key strings, or null if the JSONObject - * is empty. - */ - public JSONArray names() { - JSONArray ja = new JSONArray(); - Iterator keys = this.keys(); - while (keys.hasNext()) { - ja.put(keys.next()); - } - return ja.length() == 0 ? null : ja; + /** + * Get an optional value associated with a key. + * + * @param key A key string. + * @return An object which is the value, or null if there is no value. + */ + public Object opt(String key) { + return key == null ? null : this.map.get(key); + } + + /** + * Get an optional boolean associated with a key. It returns false if there is no such key, or if + * the value is not Boolean.TRUE or the String "true". + * + * @param key A key string. + * @return The truth. + */ + public boolean optBoolean(String key) { + return optBoolean(key, false); + } + + /** + * Get an optional boolean associated with a key. It returns the defaultValue if there is no such + * key, or if it is not a Boolean or the String "true" or "false" (case insensitive). + * + * @param key A key string. + * @param defaultValue The default. + * @return The truth. + */ + public boolean optBoolean(String key, boolean defaultValue) { + try { + return getBoolean(key); + } catch (Exception e) { + return defaultValue; } + } - /** - * Get an optional value associated with a key. - * - * @param key A key string. - * @return An object which is the value, or null if there is no value. - */ - public Object opt(String key) { - return key == null ? null : this.map.get(key); + /** + * Get an optional double associated with a key, or NaN if there is no such key or if its value is + * not a number. If the value is a string, an attempt will be made to evaluate it as a number. + * + * @param key A string which is the key. + * @return An object which is the value. + */ + public double optDouble(String key) { + return optDouble(key, Double.NaN); + } + + /** + * Get an optional double associated with a key, or the defaultValue if there is no such key or if + * its value is not a number. If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public double optDouble(String key, double defaultValue) { + try { + return getDouble(key); + } catch (Exception e) { + return defaultValue; } + } - /** - * Get an optional boolean associated with a key. - * It returns false if there is no such key, or if the value is not - * Boolean.TRUE or the String "true". - * - * @param key A key string. - * @return The truth. - */ - public boolean optBoolean(String key) { - return optBoolean(key, false); + /** + * Get an optional int value associated with a key, or zero if there is no such key or if the + * value is not a number. If the value is a string, an attempt will be made to evaluate it as a + * number. + * + * @param key A key string. + * @return An object which is the value. + */ + public int optInt(String key) { + return optInt(key, 0); + } + + /** + * Get an optional int value associated with a key, or the default if there is no such key or if + * the value is not a number. If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public int optInt(String key, int defaultValue) { + try { + return getInt(key); + } catch (Exception e) { + return defaultValue; } + } - /** - * Get an optional boolean associated with a key. - * It returns the defaultValue if there is no such key, or if it is not - * a Boolean or the String "true" or "false" (case insensitive). - * - * @param key A key string. - * @param defaultValue The default. - * @return The truth. - */ - public boolean optBoolean(String key, boolean defaultValue) { - try { - return getBoolean(key); - } catch (Exception e) { - return defaultValue; - } + /** + * Get an optional JSONArray associated with a key. It returns null if there is no such key, or if + * its value is not a JSONArray. + * + * @param key A key string. + * @return A JSONArray which is the value. + */ + public JSONArray optJSONArray(String key) { + Object o = opt(key); + return o instanceof JSONArray ? (JSONArray) o : null; + } + + /** + * Get an optional JSONObject associated with a key. It returns null if there is no such key, or + * if its value is not a JSONObject. + * + * @param key A key string. + * @return A JSONObject which is the value. + */ + public JSONObject optJSONObject(String key) { + Object object = opt(key); + return object instanceof JSONObject ? (JSONObject) object : null; + } + + /** + * Get an optional long value associated with a key, or zero if there is no such key or if the + * value is not a number. If the value is a string, an attempt will be made to evaluate it as a + * number. + * + * @param key A key string. + * @return An object which is the value. + */ + public long optLong(String key) { + return optLong(key, 0); + } + + /** + * Get an optional long value associated with a key, or the default if there is no such key or if + * the value is not a number. If the value is a string, an attempt will be made to evaluate it as + * a number. + * + * @param key A key string. + * @param defaultValue The default. + * @return An object which is the value. + */ + public long optLong(String key, long defaultValue) { + try { + return getLong(key); + } catch (Exception e) { + return defaultValue; } + } - /** - * Get an optional double associated with a key, - * or NaN if there is no such key or if its value is not a number. - * If the value is a string, an attempt will be made to evaluate it as - * a number. - * - * @param key A string which is the key. - * @return An object which is the value. - */ - public double optDouble(String key) { - return optDouble(key, Double.NaN); - } + /** + * Get an optional string associated with a key. It returns an empty string if there is no such + * key. If the value is not a string and is not null, then it is converted to a string. + * + * @param key A key string. + * @return A string which is the value. + */ + public String optString(String key) { + return optString(key, ""); + } - /** - * Get an optional double associated with a key, or the - * defaultValue if there is no such key or if its value is not a number. - * If the value is a string, an attempt will be made to evaluate it as - * a number. - * - * @param key A key string. - * @param defaultValue The default. - * @return An object which is the value. - */ - public double optDouble(String key, double defaultValue) { - try { - return getDouble(key); - } catch (Exception e) { - return defaultValue; - } - } + /** + * Get an optional string associated with a key. It returns the defaultValue if there is no such + * key. + * + * @param key A key string. + * @param defaultValue The default. + * @return A string which is the value. + */ + public String optString(String key, String defaultValue) { + Object object = opt(key); + return NULL.equals(object) ? defaultValue : object.toString(); + } - /** - * Get an optional int value associated with a key, - * or zero if there is no such key or if the value is not a number. - * If the value is a string, an attempt will be made to evaluate it as - * a number. - * - * @param key A key string. - * @return An object which is the value. - */ - public int optInt(String key) { - return optInt(key, 0); - } + private void populateMap(Object bean) { + Class klass = bean.getClass(); - /** - * Get an optional int value associated with a key, - * or the default if there is no such key or if the value is not a number. - * If the value is a string, an attempt will be made to evaluate it as - * a number. - * - * @param key A key string. - * @param defaultValue The default. - * @return An object which is the value. - */ - public int optInt(String key, int defaultValue) { - try { - return getInt(key); - } catch (Exception e) { - return defaultValue; - } - } + // If klass is a System class then set includeSuperClass to false. - /** - * Get an optional JSONArray associated with a key. - * It returns null if there is no such key, or if its value is not a - * JSONArray. - * - * @param key A key string. - * @return A JSONArray which is the value. - */ - public JSONArray optJSONArray(String key) { - Object o = opt(key); - return o instanceof JSONArray ? (JSONArray) o : null; - } + boolean includeSuperClass = klass.getClassLoader() != null; - /** - * Get an optional JSONObject associated with a key. - * It returns null if there is no such key, or if its value is not a - * JSONObject. - * - * @param key A key string. - * @return A JSONObject which is the value. - */ - public JSONObject optJSONObject(String key) { - Object object = opt(key); - return object instanceof JSONObject ? (JSONObject) object : null; - } - - /** - * Get an optional long value associated with a key, - * or zero if there is no such key or if the value is not a number. - * If the value is a string, an attempt will be made to evaluate it as - * a number. - * - * @param key A key string. - * @return An object which is the value. - */ - public long optLong(String key) { - return optLong(key, 0); - } - - /** - * Get an optional long value associated with a key, - * or the default if there is no such key or if the value is not a number. - * If the value is a string, an attempt will be made to evaluate it as - * a number. - * - * @param key A key string. - * @param defaultValue The default. - * @return An object which is the value. - */ - public long optLong(String key, long defaultValue) { - try { - return getLong(key); - } catch (Exception e) { - return defaultValue; - } - } - - /** - * Get an optional string associated with a key. - * It returns an empty string if there is no such key. If the value is not - * a string and is not null, then it is converted to a string. - * - * @param key A key string. - * @return A string which is the value. - */ - public String optString(String key) { - return optString(key, ""); - } - - /** - * Get an optional string associated with a key. - * It returns the defaultValue if there is no such key. - * - * @param key A key string. - * @param defaultValue The default. - * @return A string which is the value. - */ - public String optString(String key, String defaultValue) { - Object object = opt(key); - return NULL.equals(object) ? defaultValue : object.toString(); - } - - private void populateMap(Object bean) { - Class klass = bean.getClass(); - -// If klass is a System class then set includeSuperClass to false. - - boolean includeSuperClass = klass.getClassLoader() != null; - - Method[] methods = (includeSuperClass) ? - klass.getMethods() : klass.getDeclaredMethods(); - for (int i = 0; i < methods.length; i += 1) { - try { - Method method = methods[i]; - if (Modifier.isPublic(method.getModifiers())) { - String name = method.getName(); - String key = ""; - if (name.startsWith("get")) { - if (name.equals("getClass") || - name.equals("getDeclaringClass")) { - key = ""; - } else { - key = name.substring(3); - } - } else if (name.startsWith("is")) { - key = name.substring(2); - } - if (key.length() > 0 && - Character.isUpperCase(key.charAt(0)) && - method.getParameterTypes().length == 0) { - if (key.length() == 1) { - key = key.toLowerCase(); - } else if (!Character.isUpperCase(key.charAt(1))) { - key = key.substring(0, 1).toLowerCase() + - key.substring(1); - } - - Object result = method.invoke(bean, (Object[]) null); - if (result != null) { - map.put(key, wrap(result)); - } - } - } - } catch (Exception ignore) { + Method[] methods = (includeSuperClass) ? klass.getMethods() : klass.getDeclaredMethods(); + for (int i = 0; i < methods.length; i += 1) { + try { + Method method = methods[i]; + if (Modifier.isPublic(method.getModifiers())) { + String name = method.getName(); + String key = ""; + if (name.startsWith("get")) { + if (name.equals("getClass") || name.equals("getDeclaringClass")) { + key = ""; + } else { + key = name.substring(3); } - } - } - - /** - * Put a key/boolean pair in the JSONObject. - * - * @param key A key string. - * @param value A boolean which is the value. - * @return this. - * @throws JSONException If the key is null. - */ - public JSONObject put(String key, boolean value) throws JSONException { - put(key, value ? Boolean.TRUE : Boolean.FALSE); - return this; - } - - /** - * Put a key/value pair in the JSONObject, where the value will be a - * JSONArray which is produced from a Collection. - * - * @param key A key string. - * @param value A Collection value. - * @return this. - * @throws JSONException - */ - public JSONObject put(String key, Collection value) throws JSONException { - put(key, new JSONArray(value)); - return this; - } - - /** - * Put a key/double pair in the JSONObject. - * - * @param key A key string. - * @param value A double which is the value. - * @return this. - * @throws JSONException If the key is null or if the number is invalid. - */ - public JSONObject put(String key, double value) throws JSONException { - put(key, new Double(value)); - return this; - } - - /** - * Put a key/int pair in the JSONObject. - * - * @param key A key string. - * @param value An int which is the value. - * @return this. - * @throws JSONException If the key is null. - */ - public JSONObject put(String key, int value) throws JSONException { - put(key, new Integer(value)); - return this; - } - - /** - * Put a key/long pair in the JSONObject. - * - * @param key A key string. - * @param value A long which is the value. - * @return this. - * @throws JSONException If the key is null. - */ - public JSONObject put(String key, long value) throws JSONException { - put(key, new Long(value)); - return this; - } - - /** - * Put a key/value pair in the JSONObject, where the value will be a - * JSONObject which is produced from a Map. - * - * @param key A key string. - * @param value A Map value. - * @return this. - * @throws JSONException - */ - public JSONObject put(String key, Map value) throws JSONException { - put(key, new JSONObject(value)); - return this; - } - - /** - * Put a key/value pair in the JSONObject. If the value is null, - * then the key will be removed from the JSONObject if it is present. - * - * @param key A key string. - * @param value An object which is the value. It should be of one of these - * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, - * or the JSONObject.NULL object. - * @return this. - * @throws JSONException If the value is non-finite number - * or if the key is null. - */ - public JSONObject put(String key, Object value) throws JSONException { - if (key == null) { - throw new JSONException("Null key."); - } - if (value != null) { - testValidity(value); - this.map.put(key, value); - } else { - remove(key); - } - return this; - } - - /** - * Put a key/value pair in the JSONObject, but only if the key and the - * value are both non-null, and only if there is not already a member - * with that name. - * - * @param key - * @param value - * @return his. - * @throws JSONException if the key is a duplicate - */ - public JSONObject putOnce(String key, Object value) throws JSONException { - if (key != null && value != null) { - if (opt(key) != null) { - throw new JSONException("Duplicate key \"" + key + "\""); + } else if (name.startsWith("is")) { + key = name.substring(2); + } + if (key.length() > 0 + && Character.isUpperCase(key.charAt(0)) + && method.getParameterTypes().length == 0) { + if (key.length() == 1) { + key = key.toLowerCase(); + } else if (!Character.isUpperCase(key.charAt(1))) { + key = key.substring(0, 1).toLowerCase() + key.substring(1); } - put(key, value); + + Object result = method.invoke(bean, (Object[]) null); + if (result != null) { + map.put(key, wrap(result)); + } + } } - return this; + } catch (Exception ignore) { + } } + } + + /** + * Put a key/boolean pair in the JSONObject. + * + * @param key A key string. + * @param value A boolean which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, boolean value) throws JSONException { + put(key, value ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a JSONArray which is produced + * from a Collection. + * + * @param key A key string. + * @param value A Collection value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Collection value) throws JSONException { + put(key, new JSONArray(value)); + return this; + } + + /** + * Put a key/double pair in the JSONObject. + * + * @param key A key string. + * @param value A double which is the value. + * @return this. + * @throws JSONException If the key is null or if the number is invalid. + */ + public JSONObject put(String key, double value) throws JSONException { + put(key, new Double(value)); + return this; + } + + /** + * Put a key/int pair in the JSONObject. + * + * @param key A key string. + * @param value An int which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, int value) throws JSONException { + put(key, new Integer(value)); + return this; + } + + /** + * Put a key/long pair in the JSONObject. + * + * @param key A key string. + * @param value A long which is the value. + * @return this. + * @throws JSONException If the key is null. + */ + public JSONObject put(String key, long value) throws JSONException { + put(key, new Long(value)); + return this; + } + + /** + * Put a key/value pair in the JSONObject, where the value will be a JSONObject which is produced + * from a Map. + * + * @param key A key string. + * @param value A Map value. + * @return this. + * @throws JSONException + */ + public JSONObject put(String key, Map value) throws JSONException { + put(key, new JSONObject(value)); + return this; + } + + /** + * Put a key/value pair in the JSONObject. If the value is null, then the key will be removed from + * the JSONObject if it is present. + * + * @param key A key string. + * @param value An object which is the value. It should be of one of these types: Boolean, Double, + * Integer, JSONArray, JSONObject, Long, String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException If the value is non-finite number or if the key is null. + */ + public JSONObject put(String key, Object value) throws JSONException { + if (key == null) { + throw new JSONException("Null key."); + } + if (value != null) { + testValidity(value); + this.map.put(key, value); + } else { + remove(key); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value are both non-null, + * and only if there is not already a member with that name. + * + * @param key + * @param value + * @return his. + * @throws JSONException if the key is a duplicate + */ + public JSONObject putOnce(String key, Object value) throws JSONException { + if (key != null && value != null) { + if (opt(key) != null) { + throw new JSONException("Duplicate key \"" + key + "\""); + } + put(key, value); + } + return this; + } + + /** + * Put a key/value pair in the JSONObject, but only if the key and the value are both non-null. + * + * @param key A key string. + * @param value An object which is the value. It should be of one of these types: Boolean, Double, + * Integer, JSONArray, JSONObject, Long, String, or the JSONObject.NULL object. + * @return this. + * @throws JSONException If the value is a non-finite number. + */ + public JSONObject putOpt(String key, Object value) throws JSONException { + if (key != null && value != null) { + put(key, value); + } + return this; + } + + /** + * Remove a name and its value, if present. + * + * @param key The name to be removed. + * @return The value that was associated with the name, or null if there was no value. + */ + public Object remove(String key) { + return this.map.remove(key); + } + + /** + * Produce a JSONArray containing the values of the members of this JSONObject. + * + * @param names A JSONArray containing a list of key strings. This determines the sequence of the + * values in the result. + * @return A JSONArray of values. + * @throws JSONException If any of the values are non-finite numbers. + */ + public JSONArray toJSONArray(JSONArray names) throws JSONException { + if (names == null || names.length() == 0) { + return null; + } + JSONArray ja = new JSONArray(); + for (int i = 0; i < names.length(); i += 1) { + ja.put(this.opt(names.getString(i))); + } + return ja; + } + + /** + * Make a JSON text of this JSONObject. For compactness, no whitespace is added. If this would not + * result in a syntactically correct JSON text, then null will be returned instead. + * + *

Warning: This method assumes that the data structure is acyclical. + * + * @return a printable, displayable, portable, transmittable representation of the object, + * beginning with { (left brace) and ending with } + *  (right brace). + */ + public String toString() { + try { + Iterator keys = this.keys(); + StringBuffer sb = new StringBuffer("{"); + + while (keys.hasNext()) { + if (sb.length() > 1) { + sb.append(','); + } + Object o = keys.next(); + sb.append(quote(o.toString())); + sb.append(':'); + sb.append(valueToString(this.map.get(o))); + } + sb.append('}'); + return sb.toString(); + } catch (Exception e) { + return null; + } + } + + /** + * Make a prettyprinted JSON text of this JSONObject. + * + *

Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor The number of spaces to add to each level of indentation. + * @return a printable, displayable, portable, transmittable representation of the object, + * beginning with { (left brace) and ending with } + *  (right brace). + * @throws JSONException If the object contains an invalid number. + */ + public String toString(int indentFactor) throws JSONException { + return toString(indentFactor, 0); + } + + /** + * Make a prettyprinted JSON text of this JSONObject. + * + *

Warning: This method assumes that the data structure is acyclical. + * + * @param indentFactor The number of spaces to add to each level of indentation. + * @param indent The indentation of the top level. + * @return a printable, displayable, transmittable representation of the object, beginning with + * { (left brace) and ending with } + *  (right brace). + * @throws JSONException If the object contains an invalid number. + */ + String toString(int indentFactor, int indent) throws JSONException { + int i; + int length = this.length(); + if (length == 0) { + return "{}"; + } + Iterator keys = this.keys(); + int newindent = indent + indentFactor; + Object object; + StringBuffer sb = new StringBuffer("{"); + if (length == 1) { + object = keys.next(); + sb.append(quote(object.toString())); + sb.append(": "); + sb.append(valueToString(this.map.get(object), indentFactor, indent)); + } else { + while (keys.hasNext()) { + object = keys.next(); + if (sb.length() > 1) { + sb.append(",\n"); + } else { + sb.append('\n'); + } + for (i = 0; i < newindent; i += 1) { + sb.append(' '); + } + sb.append(quote(object.toString())); + sb.append(": "); + sb.append(valueToString(this.map.get(object), indentFactor, newindent)); + } + if (sb.length() > 1) { + sb.append('\n'); + for (i = 0; i < indent; i += 1) { + sb.append(' '); + } + } + } + sb.append('}'); + return sb.toString(); + } + + /** + * Write the contents of the JSONObject as JSON text to a writer. For compactness, no whitespace + * is added. + * + *

Warning: This method assumes that the data structure is acyclical. + * + * @return The writer. + * @throws JSONException + */ + public Writer write(Writer writer) throws JSONException { + try { + boolean commanate = false; + Iterator keys = this.keys(); + writer.write('{'); + + while (keys.hasNext()) { + if (commanate) { + writer.write(','); + } + Object key = keys.next(); + writer.write(quote(key.toString())); + writer.write(':'); + Object value = this.map.get(key); + if (value instanceof JSONObject) { + ((JSONObject) value).write(writer); + } else if (value instanceof JSONArray) { + ((JSONArray) value).write(writer); + } else { + writer.write(valueToString(value)); + } + commanate = true; + } + writer.write('}'); + return writer; + } catch (IOException exception) { + throw new JSONException(exception); + } + } + + /** + * JSONObject.NULL is equivalent to the value that JavaScript calls null, whilst Java's null is + * equivalent to the value that JavaScript calls undefined. + */ + private static final class Null { /** - * Put a key/value pair in the JSONObject, but only if the - * key and the value are both non-null. + * There is only intended to be a single instance of the NULL object, so the clone method + * returns itself. * - * @param key A key string. - * @param value An object which is the value. It should be of one of these - * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, - * or the JSONObject.NULL object. - * @return this. - * @throws JSONException If the value is a non-finite number. + * @return NULL. */ - public JSONObject putOpt(String key, Object value) throws JSONException { - if (key != null && value != null) { - put(key, value); - } - return this; + protected final Object clone() { + return this; } /** - * Remove a name and its value, if present. + * A Null object is equal to the null value and to itself. * - * @param key The name to be removed. - * @return The value that was associated with the name, - * or null if there was no value. + * @param object An object to test for nullness. + * @return true if the object parameter is the JSONObject.NULL object or null. */ - public Object remove(String key) { - return this.map.remove(key); + public boolean equals(Object object) { + return object == null || object == this; } /** - * Produce a JSONArray containing the values of the members of this - * JSONObject. + * Get the "null" string value. * - * @param names A JSONArray containing a list of key strings. This - * determines the sequence of the values in the result. - * @return A JSONArray of values. - * @throws JSONException If any of the values are non-finite numbers. - */ - public JSONArray toJSONArray(JSONArray names) throws JSONException { - if (names == null || names.length() == 0) { - return null; - } - JSONArray ja = new JSONArray(); - for (int i = 0; i < names.length(); i += 1) { - ja.put(this.opt(names.getString(i))); - } - return ja; - } - - /** - * Make a JSON text of this JSONObject. For compactness, no whitespace - * is added. If this would not result in a syntactically correct JSON text, - * then null will be returned instead. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @return a printable, displayable, portable, transmittable - * representation of the object, beginning - * with { (left brace) and ending - * with } (right brace). + * @return The string "null". */ public String toString() { - try { - Iterator keys = this.keys(); - StringBuffer sb = new StringBuffer("{"); - - while (keys.hasNext()) { - if (sb.length() > 1) { - sb.append(','); - } - Object o = keys.next(); - sb.append(quote(o.toString())); - sb.append(':'); - sb.append(valueToString(this.map.get(o))); - } - sb.append('}'); - return sb.toString(); - } catch (Exception e) { - return null; - } + return "null"; } - - /** - * Make a prettyprinted JSON text of this JSONObject. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @param indentFactor The number of spaces to add to each level of - * indentation. - * @return a printable, displayable, portable, transmittable - * representation of the object, beginning - * with { (left brace) and ending - * with } (right brace). - * @throws JSONException If the object contains an invalid number. - */ - public String toString(int indentFactor) throws JSONException { - return toString(indentFactor, 0); - } - - /** - * Make a prettyprinted JSON text of this JSONObject. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @param indentFactor The number of spaces to add to each level of - * indentation. - * @param indent The indentation of the top level. - * @return a printable, displayable, transmittable - * representation of the object, beginning - * with { (left brace) and ending - * with } (right brace). - * @throws JSONException If the object contains an invalid number. - */ - String toString(int indentFactor, int indent) throws JSONException { - int i; - int length = this.length(); - if (length == 0) { - return "{}"; - } - Iterator keys = this.keys(); - int newindent = indent + indentFactor; - Object object; - StringBuffer sb = new StringBuffer("{"); - if (length == 1) { - object = keys.next(); - sb.append(quote(object.toString())); - sb.append(": "); - sb.append(valueToString(this.map.get(object), indentFactor, - indent)); - } else { - while (keys.hasNext()) { - object = keys.next(); - if (sb.length() > 1) { - sb.append(",\n"); - } else { - sb.append('\n'); - } - for (i = 0; i < newindent; i += 1) { - sb.append(' '); - } - sb.append(quote(object.toString())); - sb.append(": "); - sb.append(valueToString(this.map.get(object), indentFactor, - newindent)); - } - if (sb.length() > 1) { - sb.append('\n'); - for (i = 0; i < indent; i += 1) { - sb.append(' '); - } - } - } - sb.append('}'); - return sb.toString(); - } - - /** - * Write the contents of the JSONObject as JSON text to a writer. - * For compactness, no whitespace is added. - *

- * Warning: This method assumes that the data structure is acyclical. - * - * @return The writer. - * @throws JSONException - */ - public Writer write(Writer writer) throws JSONException { - try { - boolean commanate = false; - Iterator keys = this.keys(); - writer.write('{'); - - while (keys.hasNext()) { - if (commanate) { - writer.write(','); - } - Object key = keys.next(); - writer.write(quote(key.toString())); - writer.write(':'); - Object value = this.map.get(key); - if (value instanceof JSONObject) { - ((JSONObject) value).write(writer); - } else if (value instanceof JSONArray) { - ((JSONArray) value).write(writer); - } else { - writer.write(valueToString(value)); - } - commanate = true; - } - writer.write('}'); - return writer; - } catch (IOException exception) { - throw new JSONException(exception); - } - } - - /** - * JSONObject.NULL is equivalent to the value that JavaScript calls null, - * whilst Java's null is equivalent to the value that JavaScript calls - * undefined. - */ - private static final class Null { - - /** - * There is only intended to be a single instance of the NULL object, - * so the clone method returns itself. - * - * @return NULL. - */ - protected final Object clone() { - return this; - } - - /** - * A Null object is equal to the null value and to itself. - * - * @param object An object to test for nullness. - * @return true if the object parameter is the JSONObject.NULL object - * or null. - */ - public boolean equals(Object object) { - return object == null || object == this; - } - - /** - * Get the "null" string value. - * - * @return The string "null". - */ - public String toString() { - return "null"; - } - } -} \ No newline at end of file + } +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONString.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONString.java index 37c656e..4866740 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONString.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONString.java @@ -17,19 +17,17 @@ package dev.brighten.antivpn.utils.json; /** - * The JSONString interface allows a toJSONString() - * method so that a class can change the behavior of - * JSONObject.toString(), JSONArray.toString(), - * and JSONWriter.value(Object). The - * toJSONString method will be used instead of the default behavior - * of using the Object's toString() method and quoting the result. + * The JSONString interface allows a toJSONString() method so that a class + * can change the behavior of JSONObject.toString(), JSONArray.toString(), + * and JSONWriter.value(Object). The toJSONString method will + * be used instead of the default behavior of using the Object's toString() method and + * quoting the result. */ public interface JSONString { - /** - * The toJSONString method allows a class to produce its own JSON - * serialization. - * - * @return A strictly syntactically correct JSON text. - */ - public String toJSONString(); + /** + * The toJSONString method allows a class to produce its own JSON serialization. + * + * @return A strictly syntactically correct JSON text. + */ + String toJSONString(); } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONStringer.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONStringer.java index 052c6e0..c7ae9c5 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONStringer.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONStringer.java @@ -19,54 +19,53 @@ package dev.brighten.antivpn.utils.json; import java.io.StringWriter; /** - * JSONStringer provides a quick and convenient way of producing JSON text. - * The texts produced strictly conform to JSON syntax rules. No whitespace is - * added, so the results are ready for transmission or storage. Each instance of - * JSONStringer can produce one JSON text. - *

- * A JSONStringer instance provides a value method for appending - * values to the - * text, and a key - * method for adding keys before values in objects. There are array - * and endArray methods that make and bound array values, and - * object and endObject methods which make and bound - * object values. All of these methods return the JSONWriter instance, - * permitting cascade style. For example,

+ * JSONStringer provides a quick and convenient way of producing JSON text. The texts produced
+ * strictly conform to JSON syntax rules. No whitespace is added, so the results are ready for
+ * transmission or storage. Each instance of JSONStringer can produce one JSON text.
+ *
+ * 

A JSONStringer instance provides a value method for appending values to the text, + * and a key method for adding keys before values in objects. There are array + * and endArray methods that make and bound array values, and object + * and endObject methods which make and bound object values. All of these + * methods return the JSONWriter instance, permitting cascade style. For example, + * + *

  * myString = new JSONStringer()
  *     .object()
  *         .key("JSON")
  *         .value("Hello, World!")
  *     .endObject()
- *     .toString();
which produces the string
+ *     .toString();
+ * + * which produces the string + * + *
  * {"JSON":"Hello, World!"}
- *

- * The first method called must be array or object. - * There are no methods for adding commas or colons. JSONStringer adds them for - * you. Objects and arrays can be nested up to 20 levels deep. - *

- * This can sometimes be easier than using a JSONObject to build a string. + * + *

The first method called must be array or object. There are no + * methods for adding commas or colons. JSONStringer adds them for you. Objects and arrays can be + * nested up to 20 levels deep. + * + *

This can sometimes be easier than using a JSONObject to build a string. * * @author JSON.org * @version 2008-09-18 */ public class JSONStringer extends JSONWriter { - /** - * Make a fresh JSONStringer. It can be used to build one JSON text. - */ - public JSONStringer() { - super(new StringWriter()); - } + /** Make a fresh JSONStringer. It can be used to build one JSON text. */ + public JSONStringer() { + super(new StringWriter()); + } - /** - * Return the JSON text. This method is used to obtain the product of the - * JSONStringer instance. It will return null if there was a - * problem in the construction of the JSON text (such as the calls to - * array were not properly balanced with calls to - * endArray). - * - * @return The JSON text. - */ - public String toString() { - return this.mode == 'd' ? this.writer.toString() : null; - } + /** + * Return the JSON text. This method is used to obtain the product of the JSONStringer instance. + * It will return null if there was a problem in the construction of the JSON text + * (such as the calls to array were not properly balanced with calls to + * endArray). + * + * @return The JSON text. + */ + public String toString() { + return this.mode == 'd' ? this.writer.toString() : null; + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONTokener.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONTokener.java index e0235b7..9299ad8 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONTokener.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONTokener.java @@ -19,420 +19,391 @@ package dev.brighten.antivpn.utils.json; import java.io.*; /** - * A JSONTokener takes a source string and extracts characters and tokens from - * it. It is used by the JSONObject and JSONArray constructors to parse - * JSON source strings. + * A JSONTokener takes a source string and extracts characters and tokens from it. It is used by the + * JSONObject and JSONArray constructors to parse JSON source strings. * * @author JSON.org * @version 2010-12-24 */ public class JSONTokener { - private int character; - private boolean eof; - private int index; - private int line; - private char previous; - private Reader reader; - private boolean usePrevious; + private int character; + private boolean eof; + private int index; + private int line; + private char previous; + private Reader reader; + private boolean usePrevious; + /** + * Construct a JSONTokener from a Reader. + * + * @param reader A reader. + */ + public JSONTokener(Reader reader) { + this.reader = reader.markSupported() ? reader : new BufferedReader(reader); + this.eof = false; + this.usePrevious = false; + this.previous = 0; + this.index = 0; + this.character = 1; + this.line = 1; + } - /** - * Construct a JSONTokener from a Reader. - * - * @param reader A reader. - */ - public JSONTokener(Reader reader) { - this.reader = reader.markSupported() ? - reader : new BufferedReader(reader); - this.eof = false; - this.usePrevious = false; - this.previous = 0; - this.index = 0; - this.character = 1; - this.line = 1; + /** Construct a JSONTokener from an InputStream. */ + public JSONTokener(InputStream inputStream) throws JSONException { + this(new InputStreamReader(inputStream)); + } + + /** + * Construct a JSONTokener from a string. + * + * @param s A source string. + */ + public JSONTokener(String s) { + this(new StringReader(s)); + } + + /** + * Get the hex value of a character (base16). + * + * @param c A character between '0' and '9' or between 'A' and 'F' or between 'a' and 'f'. + * @return An int between 0 and 15, or -1 if c was not a hex digit. + */ + public static int dehexchar(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + if (c >= 'A' && c <= 'F') { + return c - ('A' - 10); + } + if (c >= 'a' && c <= 'f') { + return c - ('a' - 10); + } + return -1; + } + + /** + * Back up one character. This provides a sort of lookahead capability, so that you can test for a + * digit or letter before attempting to parse the next number or identifier. + */ + public void back() throws JSONException { + if (usePrevious || index <= 0) { + throw new JSONException("Stepping back two steps is not supported"); + } + this.index -= 1; + this.character -= 1; + this.usePrevious = true; + this.eof = false; + } + + public boolean end() { + return eof && !usePrevious; + } + + /** + * Determine if the source string still contains characters that next() can consume. + * + * @return true if not yet at the end of the source. + */ + public boolean more() throws JSONException { + next(); + if (end()) { + return false; + } + back(); + return true; + } + + /** + * Get the next character in the source string. + * + * @return The next character, or 0 if past the end of the source string. + */ + public char next() throws JSONException { + int c; + if (this.usePrevious) { + this.usePrevious = false; + c = this.previous; + } else { + try { + c = this.reader.read(); + } catch (IOException exception) { + throw new JSONException(exception); + } + + if (c <= 0) { // End of stream + this.eof = true; + c = 0; + } + } + this.index += 1; + if (this.previous == '\r') { + this.line += 1; + this.character = c == '\n' ? 0 : 1; + } else if (c == '\n') { + this.line += 1; + this.character = 0; + } else { + this.character += 1; + } + this.previous = (char) c; + return this.previous; + } + + /** + * Consume the next character, and check that it matches a specified character. + * + * @param c The character to match. + * @return The character. + * @throws JSONException if the character does not match. + */ + public char next(char c) throws JSONException { + char n = next(); + if (n != c) { + throw syntaxError("Expected '" + c + "' and instead saw '" + n + "'"); + } + return n; + } + + /** + * Get the next n characters. + * + * @param n The number of characters to take. + * @return A string of n characters. + * @throws JSONException Substring bounds error if there are not n characters remaining in the + * source string. + */ + public String next(int n) throws JSONException { + if (n == 0) { + return ""; } + char[] chars = new char[n]; + int pos = 0; - /** - * Construct a JSONTokener from an InputStream. - */ - public JSONTokener(InputStream inputStream) throws JSONException { - this(new InputStreamReader(inputStream)); + while (pos < n) { + chars[pos] = next(); + if (end()) { + throw syntaxError("Substring bounds error"); + } + pos += 1; } + return new String(chars); + } - - /** - * Construct a JSONTokener from a string. - * - * @param s A source string. - */ - public JSONTokener(String s) { - this(new StringReader(s)); + /** + * Get the next char in the string, skipping whitespace. + * + * @return A character, or 0 if there are no more characters. + * @throws JSONException + */ + public char nextClean() throws JSONException { + for (; ; ) { + char c = next(); + if (c == 0 || c > ' ') { + return c; + } } + } - /** - * Get the hex value of a character (base16). - * - * @param c A character between '0' and '9' or between 'A' and 'F' or - * between 'a' and 'f'. - * @return An int between 0 and 15, or -1 if c was not a hex digit. - */ - public static int dehexchar(char c) { - if (c >= '0' && c <= '9') { - return c - '0'; - } - if (c >= 'A' && c <= 'F') { - return c - ('A' - 10); - } - if (c >= 'a' && c <= 'f') { - return c - ('a' - 10); - } - return -1; - } - - /** - * Back up one character. This provides a sort of lookahead capability, - * so that you can test for a digit or letter before attempting to parse - * the next number or identifier. - */ - public void back() throws JSONException { - if (usePrevious || index <= 0) { - throw new JSONException("Stepping back two steps is not supported"); - } - this.index -= 1; - this.character -= 1; - this.usePrevious = true; - this.eof = false; - } - - public boolean end() { - return eof && !usePrevious; - } - - - /** - * Determine if the source string still contains characters that next() - * can consume. - * - * @return true if not yet at the end of the source. - */ - public boolean more() throws JSONException { - next(); - if (end()) { - return false; - } - back(); - return true; - } - - - /** - * Get the next character in the source string. - * - * @return The next character, or 0 if past the end of the source string. - */ - public char next() throws JSONException { - int c; - if (this.usePrevious) { - this.usePrevious = false; - c = this.previous; - } else { - try { - c = this.reader.read(); - } catch (IOException exception) { - throw new JSONException(exception); - } - - if (c <= 0) { // End of stream - this.eof = true; - c = 0; - } - } - this.index += 1; - if (this.previous == '\r') { - this.line += 1; - this.character = c == '\n' ? 0 : 1; - } else if (c == '\n') { - this.line += 1; - this.character = 0; - } else { - this.character += 1; - } - this.previous = (char) c; - return this.previous; - } - - - /** - * Consume the next character, and check that it matches a specified - * character. - * - * @param c The character to match. - * @return The character. - * @throws JSONException if the character does not match. - */ - public char next(char c) throws JSONException { - char n = next(); - if (n != c) { - throw syntaxError("Expected '" + c + "' and instead saw '" + - n + "'"); - } - return n; - } - - - /** - * Get the next n characters. - * - * @param n The number of characters to take. - * @return A string of n characters. - * @throws JSONException Substring bounds error if there are not - * n characters remaining in the source string. - */ - public String next(int n) throws JSONException { - if (n == 0) { - return ""; - } - - char[] chars = new char[n]; - int pos = 0; - - while (pos < n) { - chars[pos] = next(); - if (end()) { - throw syntaxError("Substring bounds error"); - } - pos += 1; - } - return new String(chars); - } - - - /** - * Get the next char in the string, skipping whitespace. - * - * @return A character, or 0 if there are no more characters. - * @throws JSONException - */ - public char nextClean() throws JSONException { - for (; ; ) { - char c = next(); - if (c == 0 || c > ' ') { - return c; - } - } - } - - - /** - * Return the characters up to the next close quote character. - * Backslash processing is done. The formal JSON format does not - * allow strings in single quotes, but an implementation is allowed to - * accept them. - * - * @param quote The quoting character, either - * " (double quote) or - * ' (single quote). - * @return A String. - * @throws JSONException Unterminated string. - */ - public String nextString(char quote) throws JSONException { - char c; - StringBuffer sb = new StringBuffer(); - for (; ; ) { - c = next(); - switch (c) { - case 0: - case '\n': - case '\r': - throw syntaxError("Unterminated string"); - case '\\': - c = next(); - switch (c) { - case 'b': - sb.append('\b'); - break; - case 't': - sb.append('\t'); - break; - case 'n': - sb.append('\n'); - break; - case 'f': - sb.append('\f'); - break; - case 'r': - sb.append('\r'); - break; - case 'u': - sb.append((char) Integer.parseInt(next(4), 16)); - break; - case '"': - case '\'': - case '\\': - case '/': - sb.append(c); - break; - default: - throw syntaxError("Illegal escape."); - } - break; - default: - if (c == quote) { - return sb.toString(); - } - sb.append(c); - } - } - } - - - /** - * Get the text up but not including the specified character or the - * end of line, whichever comes first. - * - * @param delimiter A delimiter character. - * @return A string. - */ - public String nextTo(char delimiter) throws JSONException { - StringBuffer sb = new StringBuffer(); - for (; ; ) { - char c = next(); - if (c == delimiter || c == 0 || c == '\n' || c == '\r') { - if (c != 0) { - back(); - } - return sb.toString().trim(); - } - sb.append(c); - } - } - - - /** - * Get the text up but not including one of the specified delimiter - * characters or the end of line, whichever comes first. - * - * @param delimiters A set of delimiter characters. - * @return A string, trimmed. - */ - public String nextTo(String delimiters) throws JSONException { - char c; - StringBuffer sb = new StringBuffer(); - for (; ; ) { - c = next(); - if (delimiters.indexOf(c) >= 0 || c == 0 || - c == '\n' || c == '\r') { - if (c != 0) { - back(); - } - return sb.toString().trim(); - } - sb.append(c); - } - } - - - /** - * Get the next value. The value can be a Boolean, Double, Integer, - * JSONArray, JSONObject, Long, or String, or the JSONObject.NULL object. - * - * @return An object. - * @throws JSONException If syntax error. - */ - public Object nextValue() throws JSONException { - char c = nextClean(); - String string; - - switch (c) { + /** + * Return the characters up to the next close quote character. Backslash processing is done. The + * formal JSON format does not allow strings in single quotes, but an implementation is allowed to + * accept them. + * + * @param quote The quoting character, either " (double quote) or + * ' (single quote). + * @return A String. + * @throws JSONException Unterminated string. + */ + public String nextString(char quote) throws JSONException { + char c; + StringBuffer sb = new StringBuffer(); + for (; ; ) { + c = next(); + switch (c) { + case 0: + case '\n': + case '\r': + throw syntaxError("Unterminated string"); + case '\\': + c = next(); + switch (c) { + case 'b': + sb.append('\b'); + break; + case 't': + sb.append('\t'); + break; + case 'n': + sb.append('\n'); + break; + case 'f': + sb.append('\f'); + break; + case 'r': + sb.append('\r'); + break; + case 'u': + sb.append((char) Integer.parseInt(next(4), 16)); + break; case '"': case '\'': - return nextString(c); - case '{': - back(); - return new JSONObject(this); - case '[': - back(); - return new JSONArray(this); - } + case '\\': + case '/': + sb.append(c); + break; + default: + throw syntaxError("Illegal escape."); + } + break; + default: + if (c == quote) { + return sb.toString(); + } + sb.append(c); + } + } + } - /* - * Handle unquoted text. This could be the values true, false, or - * null, or it can be a number. An implementation (such as this one) - * is allowed to also accept non-standard forms. - * - * Accumulate characters until we reach the end of the text or a - * formatting character. - */ - - StringBuffer sb = new StringBuffer(); - while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { - sb.append(c); - c = next(); + /** + * Get the text up but not including the specified character or the end of line, whichever comes + * first. + * + * @param delimiter A delimiter character. + * @return A string. + */ + public String nextTo(char delimiter) throws JSONException { + StringBuffer sb = new StringBuffer(); + for (; ; ) { + char c = next(); + if (c == delimiter || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + back(); } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the text up but not including one of the specified delimiter characters or the end of line, + * whichever comes first. + * + * @param delimiters A set of delimiter characters. + * @return A string, trimmed. + */ + public String nextTo(String delimiters) throws JSONException { + char c; + StringBuffer sb = new StringBuffer(); + for (; ; ) { + c = next(); + if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r') { + if (c != 0) { + back(); + } + return sb.toString().trim(); + } + sb.append(c); + } + } + + /** + * Get the next value. The value can be a Boolean, Double, Integer, JSONArray, JSONObject, Long, + * or String, or the JSONObject.NULL object. + * + * @return An object. + * @throws JSONException If syntax error. + */ + public Object nextValue() throws JSONException { + char c = nextClean(); + String string; + + switch (c) { + case '"': + case '\'': + return nextString(c); + case '{': back(); - - string = sb.toString().trim(); - if (string.equals("")) { - throw syntaxError("Missing value"); - } - return JSONObject.stringToValue(string); - } - - - /** - * Skip characters until the next character is the requested character. - * If the requested character is not found, no characters are skipped. - * - * @param to A character to skip to. - * @return The requested character, or zero if the requested character - * is not found. - */ - public char skipTo(char to) throws JSONException { - char c; - try { - int startIndex = this.index; - int startCharacter = this.character; - int startLine = this.line; - reader.mark(Integer.MAX_VALUE); - do { - c = next(); - if (c == 0) { - reader.reset(); - this.index = startIndex; - this.character = startCharacter; - this.line = startLine; - return c; - } - } while (c != to); - } catch (IOException exc) { - throw new JSONException(exc); - } - + return new JSONObject(this); + case '[': back(); - return c; + return new JSONArray(this); } - - /** - * Make a JSONException to signal a syntax error. + /* + * Handle unquoted text. This could be the values true, false, or + * null, or it can be a number. An implementation (such as this one) + * is allowed to also accept non-standard forms. * - * @param message The error message. - * @return A JSONException object, suitable for throwing + * Accumulate characters until we reach the end of the text or a + * formatting character. */ - public JSONException syntaxError(String message) { - return new JSONException(message + toString()); + + StringBuffer sb = new StringBuffer(); + while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { + sb.append(c); + c = next(); + } + back(); + + string = sb.toString().trim(); + if (string.isEmpty()) { + throw syntaxError("Missing value"); + } + return JSONObject.stringToValue(string); + } + + /** + * Skip characters until the next character is the requested character. If the requested character + * is not found, no characters are skipped. + * + * @param to A character to skip to. + * @return The requested character, or zero if the requested character is not found. + */ + public char skipTo(char to) throws JSONException { + char c; + try { + int startIndex = this.index; + int startCharacter = this.character; + int startLine = this.line; + reader.mark(Integer.MAX_VALUE); + do { + c = next(); + if (c == 0) { + reader.reset(); + this.index = startIndex; + this.character = startCharacter; + this.line = startLine; + return c; + } + } while (c != to); + } catch (IOException exc) { + throw new JSONException(exc); } + back(); + return c; + } - /** - * Make a printable string of this JSONTokener. - * - * @return " at {index} [character {character} line {line}]" - */ - public String toString() { - return " at " + index + " [character " + this.character + " line " + - this.line + "]"; - } -} \ No newline at end of file + /** + * Make a JSONException to signal a syntax error. + * + * @param message The error message. + * @return A JSONException object, suitable for throwing + */ + public JSONException syntaxError(String message) { + return new JSONException(message + toString()); + } + + /** + * Make a printable string of this JSONTokener. + * + * @return " at {index} [character {character} line {line}]" + */ + public String toString() { + return " at " + index + " [character " + this.character + " line " + this.line + "]"; + } +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONWriter.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONWriter.java index e57b81f..3f86d58 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONWriter.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JSONWriter.java @@ -20,305 +20,282 @@ import java.io.IOException; import java.io.Writer; /** - * JSONWriter provides a quick and convenient way of producing JSON text. - * The texts produced strictly conform to JSON syntax rules. No whitespace is - * added, so the results are ready for transmission or storage. Each instance of - * JSONWriter can produce one JSON text. - *

- * A JSONWriter instance provides a value method for appending - * values to the - * text, and a key - * method for adding keys before values in objects. There are array - * and endArray methods that make and bound array values, and - * object and endObject methods which make and bound - * object values. All of these methods return the JSONWriter instance, - * permitting a cascade style. For example,

+ * JSONWriter provides a quick and convenient way of producing JSON text. The texts produced
+ * strictly conform to JSON syntax rules. No whitespace is added, so the results are ready for
+ * transmission or storage. Each instance of JSONWriter can produce one JSON text.
+ *
+ * 

A JSONWriter instance provides a value method for appending values to the text, + * and a key method for adding keys before values in objects. There are array + * and endArray methods that make and bound array values, and object + * and endObject methods which make and bound object values. All of these + * methods return the JSONWriter instance, permitting a cascade style. For example, + * + *

  * new JSONWriter(myWriter)
  *     .object()
  *         .key("JSON")
  *         .value("Hello, World!")
- *     .endObject();
which writes
+ *     .endObject();
+ * + * which writes + * + *
  * {"JSON":"Hello, World!"}
- *

- * The first method called must be array or object. - * There are no methods for adding commas or colons. JSONWriter adds them for - * you. Objects and arrays can be nested up to 20 levels deep. - *

- * This can sometimes be easier than using a JSONObject to build a string. + * + *

The first method called must be array or object. There are no + * methods for adding commas or colons. JSONWriter adds them for you. Objects and arrays can be + * nested up to 20 levels deep. + * + *

This can sometimes be easier than using a JSONObject to build a string. * * @author JSON.org * @version 2010-12-24 */ public class JSONWriter { - private static final int maxdepth = 20; - /** - * The current mode. Values: - * 'a' (array), - * 'd' (done), - * 'i' (initial), - * 'k' (key), - * 'o' (object). - */ - protected char mode; - /** - * The writer that will receive the output. - */ - protected Writer writer; - /** - * The comma flag determines if a comma should be output before the next - * value. - */ - private boolean comma; - /** - * The object/array stack. - */ - private JSONObject stack[]; - /** - * The stack top index. A value of 0 indicates that the stack is empty. - */ - private int top; + private static final int maxdepth = 20; - /** - * Make a fresh JSONWriter. It can be used to build one JSON text. - */ - public JSONWriter(Writer w) { + /** The current mode. Values: 'a' (array), 'd' (done), 'i' (initial), 'k' (key), 'o' (object). */ + protected char mode; + + /** The writer that will receive the output. */ + protected Writer writer; + + /** The comma flag determines if a comma should be output before the next value. */ + private boolean comma; + + /** The object/array stack. */ + private JSONObject stack[]; + + /** The stack top index. A value of 0 indicates that the stack is empty. */ + private int top; + + /** Make a fresh JSONWriter. It can be used to build one JSON text. */ + public JSONWriter(Writer w) { + this.comma = false; + this.mode = 'i'; + this.stack = new JSONObject[maxdepth]; + this.top = 0; + this.writer = w; + } + + /** + * Append a value. + * + * @param string A string value. + * @return this + * @throws JSONException If the value is out of sequence. + */ + private JSONWriter append(String string) throws JSONException { + if (string == null) { + throw new JSONException("Null pointer"); + } + if (this.mode == 'o' || this.mode == 'a') { + try { + if (this.comma && this.mode == 'a') { + this.writer.write(','); + } + this.writer.write(string); + } catch (IOException e) { + throw new JSONException(e); + } + if (this.mode == 'o') { + this.mode = 'k'; + } + this.comma = true; + return this; + } + throw new JSONException("Value out of sequence."); + } + + /** + * Begin appending a new array. All values until the balancing endArray will be + * appended to this array. The endArray method must be called to mark the array's + * end. + * + * @return this + * @throws JSONException If the nesting is too deep, or if the object is started in the wrong + * place (for example as a key or after the end of the outermost array or object). + */ + public JSONWriter array() throws JSONException { + if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') { + this.push(null); + this.append("["); + this.comma = false; + return this; + } + throw new JSONException("Misplaced array."); + } + + /** + * End something. + * + * @param mode Mode + * @param c Closing character + * @return this + * @throws JSONException If unbalanced. + */ + private JSONWriter end(char mode, char c) throws JSONException { + if (this.mode != mode) { + throw new JSONException(mode == 'a' ? "Misplaced endArray." : "Misplaced endObject."); + } + this.pop(mode); + try { + this.writer.write(c); + } catch (IOException e) { + throw new JSONException(e); + } + this.comma = true; + return this; + } + + /** + * End an array. This method most be called to balance calls to array. + * + * @return this + * @throws JSONException If incorrectly nested. + */ + public JSONWriter endArray() throws JSONException { + return this.end('a', ']'); + } + + /** + * End an object. This method most be called to balance calls to object. + * + * @return this + * @throws JSONException If incorrectly nested. + */ + public JSONWriter endObject() throws JSONException { + return this.end('k', '}'); + } + + /** + * Append a key. The key will be associated with the next value. In an object, every value must be + * preceded by a key. + * + * @param string A key string. + * @return this + * @throws JSONException If the key is out of place. For example, keys do not belong in arrays or + * if the key is null. + */ + public JSONWriter key(String string) throws JSONException { + if (string == null) { + throw new JSONException("Null key."); + } + if (this.mode == 'k') { + try { + stack[top - 1].putOnce(string, Boolean.TRUE); + if (this.comma) { + this.writer.write(','); + } + this.writer.write(JSONObject.quote(string)); + this.writer.write(':'); this.comma = false; - this.mode = 'i'; - this.stack = new JSONObject[maxdepth]; - this.top = 0; - this.writer = w; - } - - /** - * Append a value. - * - * @param string A string value. - * @return this - * @throws JSONException If the value is out of sequence. - */ - private JSONWriter append(String string) throws JSONException { - if (string == null) { - throw new JSONException("Null pointer"); - } - if (this.mode == 'o' || this.mode == 'a') { - try { - if (this.comma && this.mode == 'a') { - this.writer.write(','); - } - this.writer.write(string); - } catch (IOException e) { - throw new JSONException(e); - } - if (this.mode == 'o') { - this.mode = 'k'; - } - this.comma = true; - return this; - } - throw new JSONException("Value out of sequence."); - } - - /** - * Begin appending a new array. All values until the balancing - * endArray will be appended to this array. The - * endArray method must be called to mark the array's end. - * - * @return this - * @throws JSONException If the nesting is too deep, or if the object is - * started in the wrong place (for example as a key or after the end of the - * outermost array or object). - */ - public JSONWriter array() throws JSONException { - if (this.mode == 'i' || this.mode == 'o' || this.mode == 'a') { - this.push(null); - this.append("["); - this.comma = false; - return this; - } - throw new JSONException("Misplaced array."); - } - - /** - * End something. - * - * @param mode Mode - * @param c Closing character - * @return this - * @throws JSONException If unbalanced. - */ - private JSONWriter end(char mode, char c) throws JSONException { - if (this.mode != mode) { - throw new JSONException(mode == 'a' ? "Misplaced endArray." : - "Misplaced endObject."); - } - this.pop(mode); - try { - this.writer.write(c); - } catch (IOException e) { - throw new JSONException(e); - } - this.comma = true; + this.mode = 'o'; return this; + } catch (IOException e) { + throw new JSONException(e); + } } + throw new JSONException("Misplaced key."); + } - /** - * End an array. This method most be called to balance calls to - * array. - * - * @return this - * @throws JSONException If incorrectly nested. - */ - public JSONWriter endArray() throws JSONException { - return this.end('a', ']'); + /** + * Begin appending a new object. All keys and values until the balancing endObject + * will be appended to this object. The endObject method must be called to mark the + * object's end. + * + * @return this + * @throws JSONException If the nesting is too deep, or if the object is started in the wrong + * place (for example as a key or after the end of the outermost array or object). + */ + public JSONWriter object() throws JSONException { + if (this.mode == 'i') { + this.mode = 'o'; } - - /** - * End an object. This method most be called to balance calls to - * object. - * - * @return this - * @throws JSONException If incorrectly nested. - */ - public JSONWriter endObject() throws JSONException { - return this.end('k', '}'); + if (this.mode == 'o' || this.mode == 'a') { + this.append("{"); + this.push(new JSONObject()); + this.comma = false; + return this; } + throw new JSONException("Misplaced object."); + } - /** - * Append a key. The key will be associated with the next value. In an - * object, every value must be preceded by a key. - * - * @param string A key string. - * @return this - * @throws JSONException If the key is out of place. For example, keys - * do not belong in arrays or if the key is null. - */ - public JSONWriter key(String string) throws JSONException { - if (string == null) { - throw new JSONException("Null key."); - } - if (this.mode == 'k') { - try { - stack[top - 1].putOnce(string, Boolean.TRUE); - if (this.comma) { - this.writer.write(','); - } - this.writer.write(JSONObject.quote(string)); - this.writer.write(':'); - this.comma = false; - this.mode = 'o'; - return this; - } catch (IOException e) { - throw new JSONException(e); - } - } - throw new JSONException("Misplaced key."); + /** + * Pop an array or object scope. + * + * @param c The scope to close. + * @throws JSONException If nesting is wrong. + */ + private void pop(char c) throws JSONException { + if (this.top <= 0) { + throw new JSONException("Nesting error."); } - - - /** - * Begin appending a new object. All keys and values until the balancing - * endObject will be appended to this object. The - * endObject method must be called to mark the object's end. - * - * @return this - * @throws JSONException If the nesting is too deep, or if the object is - * started in the wrong place (for example as a key or after the end of the - * outermost array or object). - */ - public JSONWriter object() throws JSONException { - if (this.mode == 'i') { - this.mode = 'o'; - } - if (this.mode == 'o' || this.mode == 'a') { - this.append("{"); - this.push(new JSONObject()); - this.comma = false; - return this; - } - throw new JSONException("Misplaced object."); - + char m = this.stack[this.top - 1] == null ? 'a' : 'k'; + if (m != c) { + throw new JSONException("Nesting error."); } + this.top -= 1; + this.mode = this.top == 0 ? 'd' : this.stack[this.top - 1] == null ? 'a' : 'k'; + } - - /** - * Pop an array or object scope. - * - * @param c The scope to close. - * @throws JSONException If nesting is wrong. - */ - private void pop(char c) throws JSONException { - if (this.top <= 0) { - throw new JSONException("Nesting error."); - } - char m = this.stack[this.top - 1] == null ? 'a' : 'k'; - if (m != c) { - throw new JSONException("Nesting error."); - } - this.top -= 1; - this.mode = this.top == 0 ? - 'd' : this.stack[this.top - 1] == null ? 'a' : 'k'; + /** + * Push an array or object scope. + * + * @param c The scope to open. + * @throws JSONException If nesting is too deep. + */ + private void push(JSONObject jo) throws JSONException { + if (this.top >= maxdepth) { + throw new JSONException("Nesting too deep."); } + this.stack[this.top] = jo; + this.mode = jo == null ? 'a' : 'k'; + this.top += 1; + } - /** - * Push an array or object scope. - * - * @param c The scope to open. - * @throws JSONException If nesting is too deep. - */ - private void push(JSONObject jo) throws JSONException { - if (this.top >= maxdepth) { - throw new JSONException("Nesting too deep."); - } - this.stack[this.top] = jo; - this.mode = jo == null ? 'a' : 'k'; - this.top += 1; - } + /** + * Append either the value true or the value false. + * + * @param b A boolean. + * @return this + * @throws JSONException + */ + public JSONWriter value(boolean b) throws JSONException { + return this.append(b ? "true" : "false"); + } + /** + * Append a double value. + * + * @param d A double. + * @return this + * @throws JSONException If the number is not finite. + */ + public JSONWriter value(double d) throws JSONException { + return this.value(new Double(d)); + } - /** - * Append either the value true or the value - * false. - * - * @param b A boolean. - * @return this - * @throws JSONException - */ - public JSONWriter value(boolean b) throws JSONException { - return this.append(b ? "true" : "false"); - } + /** + * Append a long value. + * + * @param l A long. + * @return this + * @throws JSONException + */ + public JSONWriter value(long l) throws JSONException { + return this.append(Long.toString(l)); + } - /** - * Append a double value. - * - * @param d A double. - * @return this - * @throws JSONException If the number is not finite. - */ - public JSONWriter value(double d) throws JSONException { - return this.value(new Double(d)); - } - - /** - * Append a long value. - * - * @param l A long. - * @return this - * @throws JSONException - */ - public JSONWriter value(long l) throws JSONException { - return this.append(Long.toString(l)); - } - - - /** - * Append an object value. - * - * @param object The object to append. It can be null, or a Boolean, Number, - * String, JSONObject, or JSONArray, or an object that implements JSONString. - * @return this - * @throws JSONException If the value is out of sequence. - */ - public JSONWriter value(Object object) throws JSONException { - return this.append(JSONObject.valueToString(object)); - } + /** + * Append an object value. + * + * @param object The object to append. It can be null, or a Boolean, Number, String, JSONObject, + * or JSONArray, or an object that implements JSONString. + * @return this + * @throws JSONException If the value is out of sequence. + */ + public JSONWriter value(Object object) throws JSONException { + return this.append(JSONObject.valueToString(object)); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JsonReader.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JsonReader.java index 25d667c..c2dcf5a 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JsonReader.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/JsonReader.java @@ -15,30 +15,31 @@ */ package dev.brighten.antivpn.utils.json; + import java.io.*; import java.net.URL; import java.nio.charset.Charset; public class JsonReader { - public static String readAll(Reader rd) throws IOException { - StringBuilder sb = new StringBuilder(); - int cp; - while ((cp = rd.read()) != -1) { - sb.append((char) cp); - } - return sb.toString(); + public static String readAll(Reader rd) throws IOException { + StringBuilder sb = new StringBuilder(); + int cp; + while ((cp = rd.read()) != -1) { + sb.append((char) cp); } + return sb.toString(); + } - public static JSONObject readJsonFromUrl(String url) throws IOException, JSONException { - InputStream is = new URL(url).openStream(); - try { - BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8"))); - String jsonText = readAll(rd); - JSONObject json = new JSONObject(jsonText); - return json; - } finally { - is.close(); - } + public static JSONObject readJsonFromUrl(String url) throws IOException, JSONException { + InputStream is = new URL(url).openStream(); + try { + BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8"))); + String jsonText = readAll(rd); + JSONObject json = new JSONObject(jsonText); + return json; + } finally { + is.close(); } -} \ No newline at end of file + } +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/XML.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/XML.java index 96852ad..a8a7030 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/XML.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/XML.java @@ -18,509 +18,479 @@ package dev.brighten.antivpn.utils.json; import java.util.Iterator; - /** - * This provides static methods to convert an XML text into a JSONObject, - * and to covert a JSONObject into an XML text. + * This provides static methods to convert an XML text into a JSONObject, and to covert a JSONObject + * into an XML text. * * @author JSON.org * @version 2011-02-11 */ public class XML { - /** - * The Character '&'. - */ - public static final Character AMP = new Character('&'); + /** The Character '&'. */ + public static final Character AMP = new Character('&'); - /** - * The Character '''. - */ - public static final Character APOS = new Character('\''); + /** The Character '''. */ + public static final Character APOS = new Character('\''); - /** - * The Character '!'. - */ - public static final Character BANG = new Character('!'); + /** The Character '!'. */ + public static final Character BANG = new Character('!'); - /** - * The Character '='. - */ - public static final Character EQ = new Character('='); + /** The Character '='. */ + public static final Character EQ = new Character('='); - /** - * The Character '>'. - */ - public static final Character GT = new Character('>'); + /** The Character '>'. */ + public static final Character GT = new Character('>'); - /** - * The Character '<'. - */ - public static final Character LT = new Character('<'); + /** The Character '<'. */ + public static final Character LT = new Character('<'); - /** - * The Character '?'. - */ - public static final Character QUEST = new Character('?'); + /** The Character '?'. */ + public static final Character QUEST = new Character('?'); - /** - * The Character '"'. - */ - public static final Character QUOT = new Character('"'); + /** The Character '"'. */ + public static final Character QUOT = new Character('"'); - /** - * The Character '/'. - */ - public static final Character SLASH = new Character('/'); + /** The Character '/'. */ + public static final Character SLASH = new Character('/'); - /** - * Replace special characters with XML escapes: - *

-     * & (ampersand) is replaced by &amp;
-     * < (less than) is replaced by &lt;
-     * > (greater than) is replaced by &gt;
-     * " (double quote) is replaced by &quot;
-     * 
- * - * @param string The string to be escaped. - * @return The escaped string. - */ - public static String escape(String string) { - StringBuffer sb = new StringBuffer(); - for (int i = 0, length = string.length(); i < length; i++) { - char c = string.charAt(i); - switch (c) { - case '&': - sb.append("&"); - break; - case '<': - sb.append("<"); - break; - case '>': - sb.append(">"); - break; - case '"': - sb.append("""); - break; - case '\'': - sb.append("'"); - break; - default: - sb.append(c); + /** + * Replace special characters with XML escapes: + * + *
+   * & (ampersand) is replaced by &amp;
+   * < (less than) is replaced by &lt;
+   * > (greater than) is replaced by &gt;
+   * " (double quote) is replaced by &quot;
+   * 
+ * + * @param string The string to be escaped. + * @return The escaped string. + */ + public static String escape(String string) { + StringBuffer sb = new StringBuffer(); + for (int i = 0, length = string.length(); i < length; i++) { + char c = string.charAt(i); + switch (c) { + case '&': + sb.append("&"); + break; + case '<': + sb.append("<"); + break; + case '>': + sb.append(">"); + break; + case '"': + sb.append("""); + break; + case '\'': + sb.append("'"); + break; + default: + sb.append(c); + } + } + return sb.toString(); + } + + /** + * Throw an exception if the string contains whitespace. Whitespace is not allowed in tagNames and + * attributes. + * + * @param string + * @throws JSONException + */ + public static void noSpace(String string) throws JSONException { + int i, length = string.length(); + if (length == 0) { + throw new JSONException("Empty string."); + } + for (i = 0; i < length; i += 1) { + if (Character.isWhitespace(string.charAt(i))) { + throw new JSONException("'" + string + "' contains a space character."); + } + } + } + + /** + * Scan the content following the named tag, attaching it to the context. + * + * @param x The XMLTokener containing the source string. + * @param context The JSONObject that will include the new material. + * @param name The tag name. + * @return true if the close tag is processed. + * @throws JSONException + */ + private static boolean parse(XMLTokener x, JSONObject context, String name) throws JSONException { + char c; + int i; + JSONObject jsonobject = null; + String string; + String tagName; + Object token; + + // Test for and skip past these forms: + // + // + // + // + // Report errors for these forms: + // <> + // <= + // << + + token = x.nextToken(); + + // "); + return false; + } + x.back(); + } else if (c == '[') { + token = x.nextToken(); + if (token.equals("CDATA")) { + if (x.next() == '[') { + string = x.nextCDATA(); + if (string.length() > 0) { + context.accumulate("content", string); } + return false; + } + } + throw x.syntaxError("Expected 'CDATA['"); + } + i = 1; + do { + token = x.nextMeta(); + if (token == null) { + throw x.syntaxError("Missing '>' after ' 0); + return false; + } else if (token == QUEST) { + + // "); + return false; + } else if (token == SLASH) { + + // Close tag + + } else if (token == SLASH) { + if (x.nextToken() != GT) { + throw x.syntaxError("Misshaped tag"); + } + if (jsonobject.length() > 0) { + context.accumulate(tagName, jsonobject); + } else { + context.accumulate(tagName, ""); + } + return false; + + // Content, between <...> and + + } else if (token == GT) { + for (; ; ) { + token = x.nextContent(); + if (token == null) { + if (tagName != null) { + throw x.syntaxError("Unclosed tag " + tagName); + } + return false; + } else if (token instanceof String) { + string = (String) token; + if (string.length() > 0) { + jsonobject.accumulate("content", XML.stringToValue(string)); + } + + // Nested element + + } else if (token == LT) { + if (parse(x, jsonobject, tagName)) { + if (jsonobject.length() == 0) { + context.accumulate(tagName, ""); + } else if (jsonobject.length() == 1 && jsonobject.opt("content") != null) { + context.accumulate(tagName, jsonobject.opt("content")); + } else { + context.accumulate(tagName, jsonobject); + } + return false; + } + } + } + } else { + throw x.syntaxError("Misshaped tag"); + } + } + } + } + + /** + * Try to convert a string into a number, boolean, or null. If the string can't be converted, + * return the string. This is much less ambitious than JSONObject.stringToValue, especially + * because it does not attempt to convert plus forms, octal forms, hex forms, or E forms lacking + * decimal points. + * + * @param string A String. + * @return A simple JSON value. + */ + public static Object stringToValue(String string) { + if (string.isEmpty()) { + return string; + } + if (string.equalsIgnoreCase("true")) { + return Boolean.TRUE; + } + if (string.equalsIgnoreCase("false")) { + return Boolean.FALSE; + } + if (string.equalsIgnoreCase("null")) { + return JSONObject.NULL; + } + if (string.equals("0")) { + return new Integer(0); + } + + // If it might be a number, try converting it. If that doesn't work, + // return the string. + + try { + char initial = string.charAt(0); + boolean negative = false; + if (initial == '-') { + initial = string.charAt(1); + negative = true; + } + if (initial == '0' && string.charAt(negative ? 2 : 1) == '0') { + return string; + } + if ((initial >= '0' && initial <= '9')) { + if (string.indexOf('.') >= 0) { + return Double.valueOf(string); + } else if (string.indexOf('e') < 0 && string.indexOf('E') < 0) { + Long myLong = new Long(string); + if (myLong.longValue() == myLong.intValue()) { + return new Integer(myLong.intValue()); + } else { + return myLong; + } + } + } + } catch (Exception ignore) { + } + return string; + } + + /** + * Convert a well-formed (but not necessarily valid) XML string into a JSONObject. Some + * information may be lost in this transformation because JSON is a data format and XML is a + * document format. XML uses elements, attributes, and content text, while JSON uses unordered + * collections of name/value pairs and arrays of values. JSON does not does not like to + * distinguish between elements and attributes. Sequences of similar elements are represented as + * JSONArrays. Content text may be placed in a "content" member. Comments, prologs, DTDs, and + * <[ [ ]]> are ignored. + * + * @param string The source string. + * @return A JSONObject containing the structured data from the XML string. + * @throws JSONException + */ + public static JSONObject toJSONObject(String string) throws JSONException { + JSONObject jo = new JSONObject(); + XMLTokener x = new XMLTokener(string); + while (x.more() && x.skipPast("<")) { + parse(x, jo, null); + } + return jo; + } + + /** + * Convert a JSONObject into a well-formed, element-normal XML string. + * + * @param object A JSONObject. + * @return A string. + * @throws JSONException + */ + public static String toString(Object object) throws JSONException { + return toString(object, null); + } + + /** + * Convert a JSONObject into a well-formed, element-normal XML string. + * + * @param object A JSONObject. + * @param tagName The optional name of the enclosing tag. + * @return A string. + * @throws JSONException + */ + public static String toString(Object object, String tagName) throws JSONException { + StringBuffer sb = new StringBuffer(); + int i; + JSONArray ja; + JSONObject jo; + String key; + Iterator keys; + int length; + String string; + Object value; + if (object instanceof JSONObject) { + + // Emit + + if (tagName != null) { + sb.append('<'); + sb.append(tagName); + sb.append('>'); + } + + // Loop thru the keys. + + jo = (JSONObject) object; + keys = jo.keys(); + while (keys.hasNext()) { + key = keys.next().toString(); + value = jo.opt(key); + if (value == null) { + value = ""; + } + if (value instanceof String) { + string = (String) value; + } else { + string = null; + } + + // Emit content in body + + if (key.equals("content")) { + if (value instanceof JSONArray) { + ja = (JSONArray) value; + length = ja.length(); + for (i = 0; i < length; i += 1) { + if (i > 0) { + sb.append('\n'); + } + sb.append(escape(ja.get(i).toString())); + } + } else { + sb.append(escape(value.toString())); + } + + // Emit an array of similar keys + + } else if (value instanceof JSONArray) { + ja = (JSONArray) value; + length = ja.length(); + for (i = 0; i < length; i += 1) { + value = ja.get(i); + if (value instanceof JSONArray) { + sb.append('<'); + sb.append(key); + sb.append('>'); + sb.append(toString(value)); + sb.append("'); + } else { + sb.append(toString(value, key)); + } + } + } else if (value.equals("")) { + sb.append('<'); + sb.append(key); + sb.append("/>"); + + // Emit a new tag + + } else { + sb.append(toString(value, key)); + } + } + if (tagName != null) { + + // Emit the close tag + + sb.append("'); + } + return sb.toString(); + + // XML does not have good support for arrays. If an array appears in a place + // where XML is lacking, synthesize an element. + + } else { + if (object.getClass().isArray()) { + object = new JSONArray(object); + } + if (object instanceof JSONArray) { + ja = (JSONArray) object; + length = ja.length(); + for (i = 0; i < length; i += 1) { + sb.append(toString(ja.opt(i), tagName == null ? "array" : tagName)); } return sb.toString(); + } else { + string = (object == null) ? "null" : escape(object.toString()); + return (tagName == null) + ? "\"" + string + "\"" + : (string.isEmpty()) + ? "<" + tagName + "/>" + : "<" + tagName + ">" + string + ""; + } } - - /** - * Throw an exception if the string contains whitespace. - * Whitespace is not allowed in tagNames and attributes. - * - * @param string - * @throws JSONException - */ - public static void noSpace(String string) throws JSONException { - int i, length = string.length(); - if (length == 0) { - throw new JSONException("Empty string."); - } - for (i = 0; i < length; i += 1) { - if (Character.isWhitespace(string.charAt(i))) { - throw new JSONException("'" + string + - "' contains a space character."); - } - } - } - - /** - * Scan the content following the named tag, attaching it to the context. - * - * @param x The XMLTokener containing the source string. - * @param context The JSONObject that will include the new material. - * @param name The tag name. - * @return true if the close tag is processed. - * @throws JSONException - */ - private static boolean parse(XMLTokener x, JSONObject context, - String name) throws JSONException { - char c; - int i; - JSONObject jsonobject = null; - String string; - String tagName; - Object token; - -// Test for and skip past these forms: -// -// -// -// -// Report errors for these forms: -// <> -// <= -// << - - token = x.nextToken(); - -// "); - return false; - } - x.back(); - } else if (c == '[') { - token = x.nextToken(); - if (token.equals("CDATA")) { - if (x.next() == '[') { - string = x.nextCDATA(); - if (string.length() > 0) { - context.accumulate("content", string); - } - return false; - } - } - throw x.syntaxError("Expected 'CDATA['"); - } - i = 1; - do { - token = x.nextMeta(); - if (token == null) { - throw x.syntaxError("Missing '>' after ' 0); - return false; - } else if (token == QUEST) { - -// "); - return false; - } else if (token == SLASH) { - -// Close tag - - } else if (token == SLASH) { - if (x.nextToken() != GT) { - throw x.syntaxError("Misshaped tag"); - } - if (jsonobject.length() > 0) { - context.accumulate(tagName, jsonobject); - } else { - context.accumulate(tagName, ""); - } - return false; - -// Content, between <...> and - - } else if (token == GT) { - for (; ; ) { - token = x.nextContent(); - if (token == null) { - if (tagName != null) { - throw x.syntaxError("Unclosed tag " + tagName); - } - return false; - } else if (token instanceof String) { - string = (String) token; - if (string.length() > 0) { - jsonobject.accumulate("content", - XML.stringToValue(string)); - } - -// Nested element - - } else if (token == LT) { - if (parse(x, jsonobject, tagName)) { - if (jsonobject.length() == 0) { - context.accumulate(tagName, ""); - } else if (jsonobject.length() == 1 && - jsonobject.opt("content") != null) { - context.accumulate(tagName, - jsonobject.opt("content")); - } else { - context.accumulate(tagName, jsonobject); - } - return false; - } - } - } - } else { - throw x.syntaxError("Misshaped tag"); - } - } - } - } - - - /** - * Try to convert a string into a number, boolean, or null. If the string - * can't be converted, return the string. This is much less ambitious than - * JSONObject.stringToValue, especially because it does not attempt to - * convert plus forms, octal forms, hex forms, or E forms lacking decimal - * points. - * - * @param string A String. - * @return A simple JSON value. - */ - public static Object stringToValue(String string) { - if (string.equals("")) { - return string; - } - if (string.equalsIgnoreCase("true")) { - return Boolean.TRUE; - } - if (string.equalsIgnoreCase("false")) { - return Boolean.FALSE; - } - if (string.equalsIgnoreCase("null")) { - return JSONObject.NULL; - } - if (string.equals("0")) { - return new Integer(0); - } - -// If it might be a number, try converting it. If that doesn't work, -// return the string. - - try { - char initial = string.charAt(0); - boolean negative = false; - if (initial == '-') { - initial = string.charAt(1); - negative = true; - } - if (initial == '0' && string.charAt(negative ? 2 : 1) == '0') { - return string; - } - if ((initial >= '0' && initial <= '9')) { - if (string.indexOf('.') >= 0) { - return Double.valueOf(string); - } else if (string.indexOf('e') < 0 && string.indexOf('E') < 0) { - Long myLong = new Long(string); - if (myLong.longValue() == myLong.intValue()) { - return new Integer(myLong.intValue()); - } else { - return myLong; - } - } - } - } catch (Exception ignore) { - } - return string; - } - - - /** - * Convert a well-formed (but not necessarily valid) XML string into a - * JSONObject. Some information may be lost in this transformation - * because JSON is a data format and XML is a document format. XML uses - * elements, attributes, and content text, while JSON uses unordered - * collections of name/value pairs and arrays of values. JSON does not - * does not like to distinguish between elements and attributes. - * Sequences of similar elements are represented as JSONArrays. Content - * text may be placed in a "content" member. Comments, prologs, DTDs, and - * <[ [ ]]> are ignored. - * - * @param string The source string. - * @return A JSONObject containing the structured data from the XML string. - * @throws JSONException - */ - public static JSONObject toJSONObject(String string) throws JSONException { - JSONObject jo = new JSONObject(); - XMLTokener x = new XMLTokener(string); - while (x.more() && x.skipPast("<")) { - parse(x, jo, null); - } - return jo; - } - - - /** - * Convert a JSONObject into a well-formed, element-normal XML string. - * - * @param object A JSONObject. - * @return A string. - * @throws JSONException - */ - public static String toString(Object object) throws JSONException { - return toString(object, null); - } - - - /** - * Convert a JSONObject into a well-formed, element-normal XML string. - * - * @param object A JSONObject. - * @param tagName The optional name of the enclosing tag. - * @return A string. - * @throws JSONException - */ - public static String toString(Object object, String tagName) - throws JSONException { - StringBuffer sb = new StringBuffer(); - int i; - JSONArray ja; - JSONObject jo; - String key; - Iterator keys; - int length; - String string; - Object value; - if (object instanceof JSONObject) { - -// Emit - - if (tagName != null) { - sb.append('<'); - sb.append(tagName); - sb.append('>'); - } - -// Loop thru the keys. - - jo = (JSONObject) object; - keys = jo.keys(); - while (keys.hasNext()) { - key = keys.next().toString(); - value = jo.opt(key); - if (value == null) { - value = ""; - } - if (value instanceof String) { - string = (String) value; - } else { - string = null; - } - -// Emit content in body - - if (key.equals("content")) { - if (value instanceof JSONArray) { - ja = (JSONArray) value; - length = ja.length(); - for (i = 0; i < length; i += 1) { - if (i > 0) { - sb.append('\n'); - } - sb.append(escape(ja.get(i).toString())); - } - } else { - sb.append(escape(value.toString())); - } - -// Emit an array of similar keys - - } else if (value instanceof JSONArray) { - ja = (JSONArray) value; - length = ja.length(); - for (i = 0; i < length; i += 1) { - value = ja.get(i); - if (value instanceof JSONArray) { - sb.append('<'); - sb.append(key); - sb.append('>'); - sb.append(toString(value)); - sb.append("'); - } else { - sb.append(toString(value, key)); - } - } - } else if (value.equals("")) { - sb.append('<'); - sb.append(key); - sb.append("/>"); - -// Emit a new tag - - } else { - sb.append(toString(value, key)); - } - } - if (tagName != null) { - -// Emit the close tag - - sb.append("'); - } - return sb.toString(); - -// XML does not have good support for arrays. If an array appears in a place -// where XML is lacking, synthesize an element. - - } else { - if (object.getClass().isArray()) { - object = new JSONArray(object); - } - if (object instanceof JSONArray) { - ja = (JSONArray) object; - length = ja.length(); - for (i = 0; i < length; i += 1) { - sb.append(toString(ja.opt(i), tagName == null ? "array" : tagName)); - } - return sb.toString(); - } else { - string = (object == null) ? "null" : escape(object.toString()); - return (tagName == null) ? "\"" + string + "\"" : - (string.length() == 0) ? "<" + tagName + "/>" : - "<" + tagName + ">" + string + ""; - } - } - } -} \ No newline at end of file + } +} diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/XMLTokener.java b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/XMLTokener.java index f67c5e5..1c1db43 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/XMLTokener.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/utils/json/XMLTokener.java @@ -17,349 +17,335 @@ package dev.brighten.antivpn.utils.json; /** - * The XMLTokener extends the JSONTokener to provide additional methods - * for the parsing of XML texts. + * The XMLTokener extends the JSONTokener to provide additional methods for the parsing of XML + * texts. * * @author JSON.org * @version 2010-12-24 */ public class XMLTokener extends JSONTokener { + /** + * The table of entity values. It initially contains Character values for amp, apos, gt, lt, quot. + */ + public static final java.util.HashMap entity; - /** - * The table of entity values. It initially contains Character values for - * amp, apos, gt, lt, quot. - */ - public static final java.util.HashMap entity; + static { + entity = new java.util.HashMap(8); + entity.put("amp", XML.AMP); + entity.put("apos", XML.APOS); + entity.put("gt", XML.GT); + entity.put("lt", XML.LT); + entity.put("quot", XML.QUOT); + } - static { - entity = new java.util.HashMap(8); - entity.put("amp", XML.AMP); - entity.put("apos", XML.APOS); - entity.put("gt", XML.GT); - entity.put("lt", XML.LT); - entity.put("quot", XML.QUOT); + /** + * Construct an XMLTokener from a string. + * + * @param s A source string. + */ + public XMLTokener(String s) { + super(s); + } + + /** + * Get the text in the CDATA block. + * + * @return The string up to the ]]>. + * @throws JSONException If the ]]> is not found. + */ + public String nextCDATA() throws JSONException { + char c; + int i; + StringBuffer sb = new StringBuffer(); + for (; ; ) { + c = next(); + if (end()) { + throw syntaxError("Unclosed CDATA"); + } + sb.append(c); + i = sb.length() - 3; + if (i >= 0 && sb.charAt(i) == ']' && sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') { + sb.setLength(i); + return sb.toString(); + } } + } - /** - * Construct an XMLTokener from a string. - * - * @param s A source string. - */ - public XMLTokener(String s) { - super(s); + /** + * Get the next XML outer token, trimming whitespace. There are two kinds of tokens: the '<' + * character which begins a markup tag, and the content text between markup tags. + * + * @return A string, or a '<' Character, or null if there is no more source text. + * @throws JSONException + */ + public Object nextContent() throws JSONException { + char c; + StringBuffer sb; + do { + c = next(); + } while (Character.isWhitespace(c)); + if (c == 0) { + return null; } + if (c == '<') { + return XML.LT; + } + sb = new StringBuffer(); + for (; ; ) { + if (c == '<' || c == 0) { + back(); + return sb.toString().trim(); + } + if (c == '&') { + sb.append(nextEntity(c)); + } else { + sb.append(c); + } + c = next(); + } + } - /** - * Get the text in the CDATA block. - * - * @return The string up to the ]]>. - * @throws JSONException If the ]]> is not found. - */ - public String nextCDATA() throws JSONException { - char c; - int i; - StringBuffer sb = new StringBuffer(); + /** + * Return the next entity. These entities are translated to Characters: + * & ' > < ". + * + * @param ampersand An ampersand character. + * @return A Character or an entity String if the entity is not recognized. + * @throws JSONException If missing ';' in XML entity. + */ + public Object nextEntity(char ampersand) throws JSONException { + StringBuffer sb = new StringBuffer(); + for (; ; ) { + char c = next(); + if (Character.isLetterOrDigit(c) || c == '#') { + sb.append(Character.toLowerCase(c)); + } else if (c == ';') { + break; + } else { + throw syntaxError("Missing ';' in XML entity: &" + sb); + } + } + String string = sb.toString(); + Object object = entity.get(string); + return object != null ? object : ampersand + string + ";"; + } + + /** + * Returns the next XML meta token. This is used for skipping over and structures. + * + * @return Syntax characters (< > / = ! ?) are returned as Character, and strings and + * names are returned as Boolean. We don't care what the values actually are. + * @throws JSONException If a string is not properly closed or if the XML is badly structured. + */ + public Object nextMeta() throws JSONException { + char c; + char q; + do { + c = next(); + } while (Character.isWhitespace(c)); + switch (c) { + case 0: + throw syntaxError("Misshaped meta tag"); + case '<': + return XML.LT; + case '>': + return XML.GT; + case '/': + return XML.SLASH; + case '=': + return XML.EQ; + case '!': + return XML.BANG; + case '?': + return XML.QUEST; + case '"': + case '\'': + q = c; for (; ; ) { - c = next(); - if (end()) { - throw syntaxError("Unclosed CDATA"); - } - sb.append(c); - i = sb.length() - 3; - if (i >= 0 && sb.charAt(i) == ']' && - sb.charAt(i + 1) == ']' && sb.charAt(i + 2) == '>') { - sb.setLength(i); - return sb.toString(); - } + c = next(); + if (c == 0) { + throw syntaxError("Unterminated string"); + } + if (c == q) { + return Boolean.TRUE; + } + } + default: + for (; ; ) { + c = next(); + if (Character.isWhitespace(c)) { + return Boolean.TRUE; + } + switch (c) { + case 0: + case '<': + case '>': + case '/': + case '=': + case '!': + case '?': + case '"': + case '\'': + back(); + return Boolean.TRUE; + } } } + } + /** + * Get the next XML Token. These tokens are found inside of angle brackets. It may be one of these + * characters: / > = ! ? or it may be a string wrapped in single quotes or double + * quotes, or it may be a name. + * + * @return a String or a Character. + * @throws JSONException If the XML is not well formed. + */ + public Object nextToken() throws JSONException { + char c; + char q; + StringBuffer sb; + do { + c = next(); + } while (Character.isWhitespace(c)); + switch (c) { + case 0: + throw syntaxError("Misshaped element"); + case '<': + throw syntaxError("Misplaced '<'"); + case '>': + return XML.GT; + case '/': + return XML.SLASH; + case '=': + return XML.EQ; + case '!': + return XML.BANG; + case '?': + return XML.QUEST; - /** - * Get the next XML outer token, trimming whitespace. There are two kinds - * of tokens: the '<' character which begins a markup tag, and the content - * text between markup tags. - * - * @return A string, or a '<' Character, or null if there is no more - * source text. - * @throws JSONException - */ - public Object nextContent() throws JSONException { - char c; - StringBuffer sb; - do { - c = next(); - } while (Character.isWhitespace(c)); - if (c == 0) { - return null; - } - if (c == '<') { - return XML.LT; - } + // Quoted string + + case '"': + case '\'': + q = c; sb = new StringBuffer(); for (; ; ) { - if (c == '<' || c == 0) { - back(); - return sb.toString().trim(); - } - if (c == '&') { - sb.append(nextEntity(c)); - } else { - sb.append(c); - } - c = next(); + c = next(); + if (c == 0) { + throw syntaxError("Unterminated string"); + } + if (c == q) { + return sb.toString(); + } + if (c == '&') { + sb.append(nextEntity(c)); + } else { + sb.append(c); + } } - } + default: + // Name - /** - * Return the next entity. These entities are translated to Characters: - * & ' > < ". - * - * @param ampersand An ampersand character. - * @return A Character or an entity String if the entity is not recognized. - * @throws JSONException If missing ';' in XML entity. - */ - public Object nextEntity(char ampersand) throws JSONException { - StringBuffer sb = new StringBuffer(); + sb = new StringBuffer(); for (; ; ) { - char c = next(); - if (Character.isLetterOrDigit(c) || c == '#') { - sb.append(Character.toLowerCase(c)); - } else if (c == ';') { - break; - } else { - throw syntaxError("Missing ';' in XML entity: &" + sb); - } - } - String string = sb.toString(); - Object object = entity.get(string); - return object != null ? object : ampersand + string + ";"; - } - - - /** - * Returns the next XML meta token. This is used for skipping over - * and structures. - * - * @return Syntax characters (< > / = ! ?) are returned as - * Character, and strings and names are returned as Boolean. We don't care - * what the values actually are. - * @throws JSONException If a string is not properly closed or if the XML - * is badly structured. - */ - public Object nextMeta() throws JSONException { - char c; - char q; - do { - c = next(); - } while (Character.isWhitespace(c)); - switch (c) { + sb.append(c); + c = next(); + if (Character.isWhitespace(c)) { + return sb.toString(); + } + switch (c) { case 0: - throw syntaxError("Misshaped meta tag"); - case '<': - return XML.LT; + return sb.toString(); case '>': - return XML.GT; case '/': - return XML.SLASH; case '=': - return XML.EQ; case '!': - return XML.BANG; case '?': - return XML.QUEST; + case '[': + case ']': + back(); + return sb.toString(); + case '<': case '"': case '\'': - q = c; - for (; ; ) { - c = next(); - if (c == 0) { - throw syntaxError("Unterminated string"); - } - if (c == q) { - return Boolean.TRUE; - } - } - default: - for (; ; ) { - c = next(); - if (Character.isWhitespace(c)) { - return Boolean.TRUE; - } - switch (c) { - case 0: - case '<': - case '>': - case '/': - case '=': - case '!': - case '?': - case '"': - case '\'': - back(); - return Boolean.TRUE; - } - } + throw syntaxError("Bad character in a name"); + } } } + } + /** + * Skip characters until past the requested string. If it is not found, we are left at the end of + * the source with a result of false. + * + * @param to A string to skip past. + * @throws JSONException + */ + public boolean skipPast(String to) throws JSONException { + boolean b; + char c; + int i; + int j; + int offset = 0; + int length = to.length(); + char[] circle = new char[length]; - /** - * Get the next XML Token. These tokens are found inside of angle - * brackets. It may be one of these characters: / > = ! ? or it - * may be a string wrapped in single quotes or double quotes, or it may be a - * name. - * - * @return a String or a Character. - * @throws JSONException If the XML is not well formed. + /* + * First fill the circle buffer with as many characters as are in the + * to string. If we reach an early end, bail. */ - public Object nextToken() throws JSONException { - char c; - char q; - StringBuffer sb; - do { - c = next(); - } while (Character.isWhitespace(c)); - switch (c) { - case 0: - throw syntaxError("Misshaped element"); - case '<': - throw syntaxError("Misplaced '<'"); - case '>': - return XML.GT; - case '/': - return XML.SLASH; - case '=': - return XML.EQ; - case '!': - return XML.BANG; - case '?': - return XML.QUEST; -// Quoted string - - case '"': - case '\'': - q = c; - sb = new StringBuffer(); - for (; ; ) { - c = next(); - if (c == 0) { - throw syntaxError("Unterminated string"); - } - if (c == q) { - return sb.toString(); - } - if (c == '&') { - sb.append(nextEntity(c)); - } else { - sb.append(c); - } - } - default: - -// Name - - sb = new StringBuffer(); - for (; ; ) { - sb.append(c); - c = next(); - if (Character.isWhitespace(c)) { - return sb.toString(); - } - switch (c) { - case 0: - return sb.toString(); - case '>': - case '/': - case '=': - case '!': - case '?': - case '[': - case ']': - back(); - return sb.toString(); - case '<': - case '"': - case '\'': - throw syntaxError("Bad character in a name"); - } - } - } + for (i = 0; i < length; i += 1) { + c = next(); + if (c == 0) { + return false; + } + circle[i] = c; } - - - /** - * Skip characters until past the requested string. - * If it is not found, we are left at the end of the source with a result of false. - * - * @param to A string to skip past. - * @throws JSONException + /* + * We will loop, possibly for all of the remaining characters. */ - public boolean skipPast(String to) throws JSONException { - boolean b; - char c; - int i; - int j; - int offset = 0; - int length = to.length(); - char[] circle = new char[length]; - - /* - * First fill the circle buffer with as many characters as are in the - * to string. If we reach an early end, bail. - */ - - for (i = 0; i < length; i += 1) { - c = next(); - if (c == 0) { - return false; - } - circle[i] = c; + for (; ; ) { + j = offset; + b = true; + /* + * Compare the circle buffer with the to string. + */ + for (i = 0; i < length; i += 1) { + if (circle[j] != to.charAt(i)) { + b = false; + break; } - /* - * We will loop, possibly for all of the remaining characters. - */ - for (; ; ) { - j = offset; - b = true; - /* - * Compare the circle buffer with the to string. - */ - for (i = 0; i < length; i += 1) { - if (circle[j] != to.charAt(i)) { - b = false; - break; - } - j += 1; - if (j >= length) { - j -= length; - } - } - /* - * If we exit the loop with b intact, then victory is ours. - */ - if (b) { - return true; - } - /* - * Get the next character. If there isn't one, then defeat is ours. - */ - c = next(); - if (c == 0) { - return false; - } - /* - * Shove the character in the circle buffer and advance the - * circle offset. The offset is mod n. - */ - circle[offset] = c; - offset += 1; - if (offset >= length) { - offset -= length; - } + j += 1; + if (j >= length) { + j -= length; } + } + /* + * If we exit the loop with b intact, then victory is ours. + */ + if (b) { + return true; + } + /* + * Get the next character. If there isn't one, then defeat is ours. + */ + c = next(); + if (c == 0) { + return false; + } + /* + * Shove the character in the circle buffer and advance the + * circle offset. The offset is mod n. + */ + circle[offset] = c; + offset += 1; + if (offset >= length) { + offset -= length; + } } + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/web/FunkemunkyAPI.java b/Common/Source/src/main/java/dev/brighten/antivpn/web/FunkemunkyAPI.java index 38a5933..cae6897 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/web/FunkemunkyAPI.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/web/FunkemunkyAPI.java @@ -21,55 +21,60 @@ import dev.brighten.antivpn.utils.json.JSONObject; import dev.brighten.antivpn.utils.json.JsonReader; import dev.brighten.antivpn.web.objects.QueryResponse; import dev.brighten.antivpn.web.objects.VPNResponse; - import java.io.IOException; public class FunkemunkyAPI { - /** - * - * Queries ... API and returns information on the IP - * - * @param ip String - * @param license String - * @param cachedResults boolean - * @return VPNResponse - * @throws JSONException Throws when JSON response is not formatted properly. - * @throws IOException Throws when there is an error connecting to and processing information from API. - */ - public static VPNResponse getVPNResponse(String ip, String license, boolean cachedResults /* faster if set to true*/) - throws JSONException, IOException { - JSONObject result = JsonReader.readJsonFromUrl(String - .format("https://funkemunky.cc/vpn?ip=%s&license=%s&cache=%s", - ip, license.isEmpty() ? "none" : license, cachedResults)); + /** + * Queries ... API and returns information on the IP + * + * @param ip String + * @param license String + * @param cachedResults boolean + * @return VPNResponse + * @throws JSONException Throws when JSON response is not formatted properly. + * @throws IOException Throws when there is an error connecting to and processing information from + * API. + */ + public static VPNResponse getVPNResponse( + String ip, String license, boolean cachedResults /* faster if set to true*/) + throws JSONException, IOException { + JSONObject result = + JsonReader.readJsonFromUrl( + String.format( + "https://funkemunky.cc/vpn?ip=%s&license=%s&cache=%s", + ip, license.isEmpty() ? "none" : license, cachedResults)); - return VPNResponse.fromJson(result); - } + return VPNResponse.fromJson(result); + } - /** - * Feeds into {@link FunkemunkyAPI#getQueryResponse(String)} using "none" as argument - * to grab query information based on the connecting IP address. - * - * @return QueryResponse - * @throws JSONException Throws when JSON response is not formatted properly. - * @throws IOException Throws when there is an error connecting to and processing information from API. - */ - public static QueryResponse getQueryResponse() throws JSONException, IOException { - return getQueryResponse("none"); - } + /** + * Feeds into {@link FunkemunkyAPI#getQueryResponse(String)} using "none" as argument to grab + * query information based on the connecting IP address. + * + * @return QueryResponse + * @throws JSONException Throws when JSON response is not formatted properly. + * @throws IOException Throws when there is an error connecting to and processing information from + * API. + */ + public static QueryResponse getQueryResponse() throws JSONException, IOException { + return getQueryResponse("none"); + } - /** - * Queries ... and returns information based on the - * provided licence input. - * - * @param license String - * @return QueryResponse - * @throws JSONException Throws when JSON response is not formatted properly. - * @throws IOException Throws when there is an error connecting to and processing information from API. - */ - public static QueryResponse getQueryResponse(String license) throws JSONException, IOException { - JSONObject result = JsonReader.readJsonFromUrl("https://funkemunky.cc/vpn/queryCheck?license=" + license); + /** + * Queries ... and returns information based on + * the provided licence input. + * + * @param license String + * @return QueryResponse + * @throws JSONException Throws when JSON response is not formatted properly. + * @throws IOException Throws when there is an error connecting to and processing information from + * API. + */ + public static QueryResponse getQueryResponse(String license) throws JSONException, IOException { + JSONObject result = + JsonReader.readJsonFromUrl("https://funkemunky.cc/vpn/queryCheck?license=" + license); - return QueryResponse.fromJson(result); - } + return QueryResponse.fromJson(result); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/web/objects/QueryResponse.java b/Common/Source/src/main/java/dev/brighten/antivpn/web/objects/QueryResponse.java index a44e186..7c793ca 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/web/objects/QueryResponse.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/web/objects/QueryResponse.java @@ -21,36 +21,39 @@ import dev.brighten.antivpn.utils.json.JSONObject; import lombok.Builder; import lombok.Data; - /** - * Used to format the JSON response from ... into an object for project use. + * Used to format the JSON response from ... into + * an object for project use. */ @Data @Builder(toBuilder = true) public class QueryResponse { - private boolean validPlan; - private String planType; - private long queries; - private long queriesMax; + private boolean validPlan; + private String planType; + private long queries; + private long queriesMax; - /** - * Formats response from ... into {@link QueryResponse} for project use. - * - * @param object JSONObject - * @return QueryResponse - * @throws JSONException Throws when JSON is not formatted properly. - */ - public static QueryResponse fromJson(JSONObject object) throws JSONException { - boolean validPlan = object.getBoolean("validPlan"); + /** + * Formats response from ... into {@link + * QueryResponse} for project use. + * + * @param object JSONObject + * @return QueryResponse + * @throws JSONException Throws when JSON is not formatted properly. + */ + public static QueryResponse fromJson(JSONObject object) throws JSONException { + boolean validPlan = object.getBoolean("validPlan"); - if(!validPlan) { // Nothing else will be returned from API if validPlan is false. - return QueryResponse.builder().validPlan(false).build(); - } - - return QueryResponse.builder().validPlan(object.getBoolean("validPlan")) - .planType(object.getString("planType")) - .queries(object.getLong("queries")) - .queriesMax(object.getLong("queryLimit")).build(); + if (!validPlan) { // Nothing else will be returned from API if validPlan is false. + return QueryResponse.builder().validPlan(false).build(); } + + return QueryResponse.builder() + .validPlan(object.getBoolean("validPlan")) + .planType(object.getString("planType")) + .queries(object.getLong("queries")) + .queriesMax(object.getLong("queryLimit")) + .build(); + } } diff --git a/Common/Source/src/main/java/dev/brighten/antivpn/web/objects/VPNResponse.java b/Common/Source/src/main/java/dev/brighten/antivpn/web/objects/VPNResponse.java index 13674e7..13dec3e 100644 --- a/Common/Source/src/main/java/dev/brighten/antivpn/web/objects/VPNResponse.java +++ b/Common/Source/src/main/java/dev/brighten/antivpn/web/objects/VPNResponse.java @@ -26,67 +26,85 @@ import lombok.Data; @AllArgsConstructor @Builder public class VPNResponse { - private String asn, ip, countryName, countryCode, city, timeZone, method, isp, failureReason = "N/A"; - private boolean proxy, cached; - private final boolean success; - private double latitude, longitude; - private long lastAccess; - private long queriesLeft; + private String asn, + ip, + countryName, + countryCode, + city, + timeZone, + method, + isp, + failureReason = "N/A"; + private boolean proxy, cached; + private final boolean success; + private double latitude, longitude; + private long lastAccess; + private long queriesLeft; - public JSONObject toJson() throws JSONException { - JSONObject json = new JSONObject(); + public JSONObject toJson() throws JSONException { + JSONObject json = new JSONObject(); - json.put("ip", ip); - json.put("countryName", countryName); - json.put("countryCode", countryCode); - json.put("city", city); - json.put("method", method); - json.put("isp", isp); - json.put("proxy", proxy); - json.put("success", success); - json.put("timeZone", timeZone); - json.put("queriesLeft", queriesLeft); - json.put("cached", cached); + json.put("ip", ip); + json.put("countryName", countryName); + json.put("countryCode", countryCode); + json.put("city", city); + json.put("method", method); + json.put("isp", isp); + json.put("proxy", proxy); + json.put("success", success); + json.put("timeZone", timeZone); + json.put("queriesLeft", queriesLeft); + json.put("cached", cached); - return json; - } - - /** - * Feeds into {@link VPNResponse#fromJson(JSONObject)} formatting the JSON {@link String} into - * a {@link JSONObject} - * - * @param json String - * @return VPNResponse - */ - public static VPNResponse fromJson(String json) throws JSONException { - return fromJson(new JSONObject(json)); - } - - public static final VPNResponse FAILED_RESPONSE = VPNResponse.builder() - .success(false) - .failureReason("Internal plugin API error.") - .build(); - - /** - * Formats response from ... into {@link VPNResponse} for project use. - * - * @param jsonObject JSONObject - * @return VPNResponse - * @throws JSONException Throws when JSON is not formatted properly. - */ - public static VPNResponse fromJson(JSONObject jsonObject) throws JSONException { - if(jsonObject.getBoolean("success")) { - return new VPNResponse(jsonObject.getString("asn"), jsonObject.getString("ip"), - jsonObject.getString("countryName"), jsonObject.getString("countryCode"), - jsonObject.getString("city"), jsonObject.getString("timeZone"), - jsonObject.has("method") ? jsonObject.getString("method") : "N/A", - jsonObject.getString("isp"), "N/A", jsonObject.getBoolean("proxy"), - jsonObject.getBoolean("cached"), jsonObject.getBoolean("success"), - jsonObject.getDouble("latitude"), jsonObject.getDouble("longitude"), - jsonObject.getLong("lastAccess"), jsonObject.getInt("queriesLeft")); - } else { - return VPNResponse.builder().success(false) - .failureReason(jsonObject.getString("failureReason")).build(); - } + return json; + } + + /** + * Feeds into {@link VPNResponse#fromJson(JSONObject)} formatting the JSON {@link String} into a + * {@link JSONObject} + * + * @param json String + * @return VPNResponse + */ + public static VPNResponse fromJson(String json) throws JSONException { + return fromJson(new JSONObject(json)); + } + + public static final VPNResponse FAILED_RESPONSE = + VPNResponse.builder().success(false).failureReason("Internal plugin API error.").build(); + + /** + * Formats response from ... into {@link VPNResponse} for + * project use. + * + * @param jsonObject JSONObject + * @return VPNResponse + * @throws JSONException Throws when JSON is not formatted properly. + */ + public static VPNResponse fromJson(JSONObject jsonObject) throws JSONException { + if (jsonObject.getBoolean("success")) { + return new VPNResponse( + jsonObject.getString("asn"), + jsonObject.getString("ip"), + jsonObject.getString("countryName"), + jsonObject.getString("countryCode"), + jsonObject.getString("city"), + jsonObject.getString("timeZone"), + jsonObject.has("method") ? jsonObject.getString("method") : "N/A", + jsonObject.getString("isp"), + "N/A", + jsonObject.getBoolean("proxy"), + jsonObject.getBoolean("cached"), + jsonObject.getBoolean("success"), + jsonObject.getDouble("latitude"), + jsonObject.getDouble("longitude"), + jsonObject.getLong("lastAccess"), + jsonObject.getInt("queriesLeft")); + } else { + return VPNResponse.builder() + .success(false) + .failureReason(jsonObject.getString("failureReason")) + .build(); } + } } diff --git a/Common/Source/src/test/java/dev/brighten/antivpn/database/DatabaseIntegrationTestSupport.java b/Common/Source/src/test/java/dev/brighten/antivpn/database/DatabaseIntegrationTestSupport.java index cdf97c6..1138c6b 100644 --- a/Common/Source/src/test/java/dev/brighten/antivpn/database/DatabaseIntegrationTestSupport.java +++ b/Common/Source/src/test/java/dev/brighten/antivpn/database/DatabaseIntegrationTestSupport.java @@ -1,18 +1,15 @@ package dev.brighten.antivpn.database; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; + import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.api.VPNConfig; import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.database.sql.utils.MySQL; import dev.brighten.antivpn.utils.CIDRUtils; import dev.brighten.antivpn.web.objects.VPNResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.IOException; import java.lang.reflect.Field; import java.nio.file.Path; import java.util.List; @@ -22,196 +19,206 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Level; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; abstract class DatabaseIntegrationTestSupport { - @TempDir - Path pluginFolder; + @TempDir Path pluginFolder; - @Mock - protected AntiVPN antiVPN; + @Mock protected AntiVPN antiVPN; - @Mock - protected VPNConfig vpnConfig; + @Mock protected VPNConfig vpnConfig; - protected TestVPNExecutor vpnExecutor; - private AutoCloseable mocks; - private final AtomicReference activeDatabase = new AtomicReference<>(); + protected TestVPNExecutor vpnExecutor; + private AutoCloseable mocks; + private final AtomicReference activeDatabase = new AtomicReference<>(); - @BeforeEach - void setUpBase() throws Exception { - mocks = MockitoAnnotations.openMocks(this); - vpnExecutor = new TestVPNExecutor(); + @BeforeEach + void setUpBase() throws Exception { + mocks = MockitoAnnotations.openMocks(this); + vpnExecutor = new TestVPNExecutor(); - when(antiVPN.getVpnConfig()).thenReturn(vpnConfig); - when(antiVPN.getExecutor()).thenReturn(vpnExecutor); - when(antiVPN.getPluginFolder()).thenReturn(pluginFolder.toFile()); - when(antiVPN.getDatabase()).thenAnswer(invocation -> activeDatabase.get()); + when(antiVPN.getVpnConfig()).thenReturn(vpnConfig); + when(antiVPN.getExecutor()).thenReturn(vpnExecutor); + when(antiVPN.getPluginFolder()).thenReturn(pluginFolder.toFile()); + when(antiVPN.getDatabase()).thenAnswer(invocation -> activeDatabase.get()); - lenient().when(vpnConfig.isDatabaseEnabled()).thenReturn(true); - lenient().when(vpnConfig.cachedResults()).thenReturn(true); - lenient().when(vpnConfig.getUsername()).thenReturn("testuser"); - lenient().when(vpnConfig.getPassword()).thenReturn("testpass"); - lenient().when(vpnConfig.getDatabaseName()).thenReturn("antivpn"); - lenient().when(vpnConfig.getIp()).thenReturn("127.0.0.1"); - lenient().when(vpnConfig.getPort()).thenReturn(-1); - lenient().when(vpnConfig.mongoDatabaseURL()).thenReturn(""); - lenient().when(vpnConfig.useDatabaseCreds()).thenReturn(false); + lenient().when(vpnConfig.isDatabaseEnabled()).thenReturn(true); + lenient().when(vpnConfig.cachedResults()).thenReturn(true); + lenient().when(vpnConfig.getUsername()).thenReturn("testuser"); + lenient().when(vpnConfig.getPassword()).thenReturn("testpass"); + lenient().when(vpnConfig.getDatabaseName()).thenReturn("antivpn"); + lenient().when(vpnConfig.getIp()).thenReturn("127.0.0.1"); + lenient().when(vpnConfig.getPort()).thenReturn(-1); + lenient().when(vpnConfig.mongoDatabaseURL()).thenReturn(""); + lenient().when(vpnConfig.useDatabaseCreds()).thenReturn(false); - setAntiVpnInstance(antiVPN); + setAntiVpnInstance(antiVPN); + } + + @AfterEach + void tearDownBase() throws Exception { + VPNDatabase database = activeDatabase.getAndSet(null); + if (database != null) { + database.shutdown(); } - @AfterEach - void tearDownBase() throws Exception { - VPNDatabase database = activeDatabase.getAndSet(null); - if (database != null) { - database.shutdown(); - } - - MySQL.shutdown(); - if (vpnExecutor != null) { - vpnExecutor.getThreadExecutor().shutdownNow(); - vpnExecutor.getThreadExecutor().awaitTermination(5, TimeUnit.SECONDS); - } - - setAntiVpnInstance(null); - - if (mocks != null) { - mocks.close(); - } + MySQL.shutdown(); + if (vpnExecutor != null) { + vpnExecutor.getThreadExecutor().shutdownNow(); + vpnExecutor.getThreadExecutor().awaitTermination(5, TimeUnit.SECONDS); } - protected void registerDatabase(VPNDatabase database) { - activeDatabase.set(database); + setAntiVpnInstance(null); + + if (mocks != null) { + mocks.close(); } + } - protected void assertDatabaseContract(VPNDatabase database) throws Exception { - registerDatabase(database); - database.init(); + protected void registerDatabase(VPNDatabase database) { + activeDatabase.set(database); + } - VPNResponse response = VPNResponse.builder() - .ip("1.2.3.4") - .asn("AS123") - .countryName("United States") - .countryCode("US") - .city("New York") - .proxy(true) - .cached(true) - .success(true) - .build(); + protected void assertDatabaseContract(VPNDatabase database) throws Exception { + registerDatabase(database); + database.init(); - database.cacheResponse(response); + VPNResponse response = + VPNResponse.builder() + .ip("1.2.3.4") + .asn("AS123") + .countryName("United States") + .countryCode("US") + .city("New York") + .proxy(true) + .cached(true) + .success(true) + .build(); - Optional storedResponse = awaitStoredResponse(database, response.getIp()); - assertTrue(storedResponse.isPresent(), "Expected cached response to be stored"); - assertEquals("AS123", storedResponse.get().getAsn()); - assertTrue(storedResponse.get().isProxy()); + database.cacheResponse(response); - database.deleteResponse(response.getIp()); - awaitCondition(() -> database.getStoredResponse(response.getIp()).isEmpty(), - "Expected cached response to be deleted"); + Optional storedResponse = awaitStoredResponse(database, response.getIp()); + assertTrue(storedResponse.isPresent(), "Expected cached response to be stored"); + assertEquals("AS123", storedResponse.get().getAsn()); + assertTrue(storedResponse.get().isProxy()); - database.cacheResponse(response); - awaitCondition(() -> database.getStoredResponse(response.getIp()).isPresent(), - "Expected cached response to be restored"); + database.deleteResponse(response.getIp()); + awaitCondition( + () -> database.getStoredResponse(response.getIp()).isEmpty(), + "Expected cached response to be deleted"); - UUID uuid = UUID.randomUUID(); - assertFalse(database.isWhitelisted(uuid)); - database.addWhitelist(uuid); - awaitCondition(() -> database.isWhitelisted(uuid), "Expected UUID whitelist entry to exist"); - List whitelisted = database.getAllWhitelisted(); - assertTrue(whitelisted.contains(uuid)); - database.removeWhitelist(uuid); - awaitCondition(() -> !database.isWhitelisted(uuid), "Expected UUID whitelist entry to be removed"); + database.cacheResponse(response); + awaitCondition( + () -> database.getStoredResponse(response.getIp()).isPresent(), + "Expected cached response to be restored"); - CIDRUtils cidr = new CIDRUtils("192.168.1.0/24"); - assertFalse(database.isWhitelisted(cidr)); - database.addWhitelist(cidr); - awaitCondition(() -> database.isWhitelisted(cidr), "Expected CIDR whitelist entry to exist"); - List whitelistedIps = database.getAllWhitelistedIps(); - assertTrue(whitelistedIps.stream().anyMatch(entry -> entry.getCidr().equals(cidr.getCidr()))); - database.removeWhitelist(cidr); - awaitCondition(() -> !database.isWhitelisted(cidr), "Expected CIDR whitelist entry to be removed"); + UUID uuid = UUID.randomUUID(); + assertFalse(database.isWhitelisted(uuid)); + database.addWhitelist(uuid); + awaitCondition(() -> database.isWhitelisted(uuid), "Expected UUID whitelist entry to exist"); + List whitelisted = database.getAllWhitelisted(); + assertTrue(whitelisted.contains(uuid)); + database.removeWhitelist(uuid); + awaitCondition( + () -> !database.isWhitelisted(uuid), "Expected UUID whitelist entry to be removed"); - database.updateAlertsState(uuid, true); - awaitCondition(() -> awaitAlertsState(database, uuid), "Expected alerts to be enabled"); - database.updateAlertsState(uuid, false); - awaitCondition(() -> !awaitAlertsState(database, uuid), "Expected alerts to be disabled"); + CIDRUtils cidr = new CIDRUtils("192.168.1.0/24"); + assertFalse(database.isWhitelisted(cidr)); + database.addWhitelist(cidr); + awaitCondition(() -> database.isWhitelisted(cidr), "Expected CIDR whitelist entry to exist"); + List whitelistedIps = database.getAllWhitelistedIps(); + assertTrue(whitelistedIps.stream().anyMatch(entry -> entry.getCidr().equals(cidr.getCidr()))); + database.removeWhitelist(cidr); + awaitCondition( + () -> !database.isWhitelisted(cidr), "Expected CIDR whitelist entry to be removed"); - database.clearResponses(); - awaitCondition(() -> database.getStoredResponse(response.getIp()).isEmpty(), - "Expected cached responses to be cleared"); - } + database.updateAlertsState(uuid, true); + awaitCondition(() -> awaitAlertsState(database, uuid), "Expected alerts to be enabled"); + database.updateAlertsState(uuid, false); + awaitCondition(() -> !awaitAlertsState(database, uuid), "Expected alerts to be disabled"); - private Optional awaitStoredResponse(VPNDatabase database, String ip) throws InterruptedException { - AtomicReference> result = new AtomicReference<>(Optional.empty()); - awaitCondition(() -> { - Optional response = database.getStoredResponse(ip); - result.set(response); - return response.isPresent(); - }, "Timed out waiting for cached response"); - return result.get(); - } + database.clearResponses(); + awaitCondition( + () -> database.getStoredResponse(response.getIp()).isEmpty(), + "Expected cached responses to be cleared"); + } - private boolean awaitAlertsState(VPNDatabase database, UUID uuid) throws InterruptedException { - CountDownLatch latch = new CountDownLatch(1); - AtomicReference result = new AtomicReference<>(false); - database.alertsState(uuid, enabled -> { - result.set(enabled); - latch.countDown(); + private Optional awaitStoredResponse(VPNDatabase database, String ip) + throws InterruptedException { + AtomicReference> result = new AtomicReference<>(Optional.empty()); + awaitCondition( + () -> { + Optional response = database.getStoredResponse(ip); + result.set(response); + return response.isPresent(); + }, + "Timed out waiting for cached response"); + return result.get(); + } + + private boolean awaitAlertsState(VPNDatabase database, UUID uuid) throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + AtomicReference result = new AtomicReference<>(false); + database.alertsState( + uuid, + enabled -> { + result.set(enabled); + latch.countDown(); }); - assertTrue(latch.await(2, TimeUnit.SECONDS), "Timed out waiting for alerts state callback"); - return result.get(); - } + assertTrue(latch.await(2, TimeUnit.SECONDS), "Timed out waiting for alerts state callback"); + return result.get(); + } - protected void awaitCondition(CheckedBooleanSupplier condition, String failureMessage) throws InterruptedException { - long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10); - while (System.nanoTime() < deadline) { - try { - if (condition.getAsBoolean()) { - return; - } - } catch (Exception e) { - fail(e.getMessage(), e); - return; - } - Thread.sleep(100); + protected void awaitCondition(CheckedBooleanSupplier condition, String failureMessage) + throws InterruptedException { + long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10); + while (System.nanoTime() < deadline) { + try { + if (condition.getAsBoolean()) { + return; } - fail(failureMessage); + } catch (Exception e) { + fail(e.getMessage(), e); + return; + } + Thread.sleep(100); } + fail(failureMessage); + } - private static void setAntiVpnInstance(AntiVPN instance) throws Exception { - Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); - instanceField.setAccessible(true); - instanceField.set(null, instance); - } + private static void setAntiVpnInstance(AntiVPN instance) throws Exception { + Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); + instanceField.setAccessible(true); + instanceField.set(null, instance); + } - @FunctionalInterface - protected interface CheckedBooleanSupplier { - boolean getAsBoolean() throws Exception; - } + @FunctionalInterface + protected interface CheckedBooleanSupplier { + boolean getAsBoolean() throws Exception; + } - protected static final class TestVPNExecutor extends VPNExecutor { - @Override - public void registerListeners() {} + protected static final class TestVPNExecutor extends VPNExecutor { + @Override + public void registerListeners() {} - @Override - public void log(Level level, String log, Object... objects) {} + @Override + public void log(Level level, String log, Object... objects) {} - @Override - public void log(String log, Object... objects) {} + @Override + public void log(String log, Object... objects) {} - @Override - public void logException(String message, Throwable ex) {} + @Override + public void logException(String message, Throwable ex) {} - @Override - public void runCommand(String command) {} + @Override + public void runCommand(String command) {} - @Override - public void disablePlugin() {} - } + @Override + public void disablePlugin() {} + } } diff --git a/Common/Source/src/test/java/dev/brighten/antivpn/database/H2DatabaseIntegrationTest.java b/Common/Source/src/test/java/dev/brighten/antivpn/database/H2DatabaseIntegrationTest.java index 1e22f64..7cdf09b 100644 --- a/Common/Source/src/test/java/dev/brighten/antivpn/database/H2DatabaseIntegrationTest.java +++ b/Common/Source/src/test/java/dev/brighten/antivpn/database/H2DatabaseIntegrationTest.java @@ -5,8 +5,8 @@ import org.junit.jupiter.api.Test; class H2DatabaseIntegrationTest extends DatabaseIntegrationTestSupport { - @Test - void h2DatabaseImplementsTheVpnDatabaseContract() throws Exception { - assertDatabaseContract(new H2VPN()); - } + @Test + void h2DatabaseImplementsTheVpnDatabaseContract() throws Exception { + assertDatabaseContract(new H2VPN()); + } } diff --git a/Common/Source/src/test/java/dev/brighten/antivpn/database/MongoDatabaseIntegrationTest.java b/Common/Source/src/test/java/dev/brighten/antivpn/database/MongoDatabaseIntegrationTest.java index 133c593..38d6e06 100644 --- a/Common/Source/src/test/java/dev/brighten/antivpn/database/MongoDatabaseIntegrationTest.java +++ b/Common/Source/src/test/java/dev/brighten/antivpn/database/MongoDatabaseIntegrationTest.java @@ -1,40 +1,39 @@ package dev.brighten.antivpn.database; -import com.mongodb.client.MongoClients; -import dev.brighten.antivpn.database.mongo.MongoVPN; -import org.junit.jupiter.api.Test; -import org.bson.Document; -import org.testcontainers.containers.MongoDBContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; - -import java.util.UUID; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; +import com.mongodb.client.MongoClients; +import dev.brighten.antivpn.database.mongo.MongoVPN; +import java.util.UUID; +import org.bson.Document; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + @Testcontainers class MongoDatabaseIntegrationTest extends DatabaseIntegrationTestSupport { - @Container - private static final MongoDBContainer MONGO = new MongoDBContainer("mongo:6.0.14"); + @Container private static final MongoDBContainer MONGO = new MongoDBContainer("mongo:6.0.14"); - @Test - void mongoDatabaseImplementsTheVpnDatabaseContract() throws Exception { - assertTrue(MONGO.isRunning(), "Mongo Testcontainer should be running"); + @Test + void mongoDatabaseImplementsTheVpnDatabaseContract() throws Exception { + assertTrue(MONGO.isRunning(), "Mongo Testcontainer should be running"); - try (var client = MongoClients.create(MONGO.getConnectionString())) { - var response = client.getDatabase("admin").runCommand(new Document("ping", 1)); - assertEquals(1.0d, response.getDouble("ok"), "Expected Mongo container to respond to ping"); - } - - when(vpnConfig.getIp()).thenReturn(MONGO.getHost()); - when(vpnConfig.getPort()).thenReturn(MONGO.getMappedPort(27017)); - when(vpnConfig.getDatabaseName()).thenReturn("antivpn_" + UUID.randomUUID().toString().replace("-", "")); - when(vpnConfig.mongoDatabaseURL()).thenReturn(""); - when(vpnConfig.useDatabaseCreds()).thenReturn(false); - - assertDatabaseContract(new MongoVPN()); + try (var client = MongoClients.create(MONGO.getConnectionString())) { + var response = client.getDatabase("admin").runCommand(new Document("ping", 1)); + assertEquals(1.0d, response.getDouble("ok"), "Expected Mongo container to respond to ping"); } + + when(vpnConfig.getIp()).thenReturn(MONGO.getHost()); + when(vpnConfig.getPort()).thenReturn(MONGO.getMappedPort(27017)); + when(vpnConfig.getDatabaseName()) + .thenReturn("antivpn_" + UUID.randomUUID().toString().replace("-", "")); + when(vpnConfig.mongoDatabaseURL()).thenReturn(""); + when(vpnConfig.useDatabaseCreds()).thenReturn(false); + + assertDatabaseContract(new MongoVPN()); + } } diff --git a/Common/Source/src/test/java/dev/brighten/antivpn/database/MySqlDatabaseIntegrationTest.java b/Common/Source/src/test/java/dev/brighten/antivpn/database/MySqlDatabaseIntegrationTest.java index 8e2898f..1aabf4e 100644 --- a/Common/Source/src/test/java/dev/brighten/antivpn/database/MySqlDatabaseIntegrationTest.java +++ b/Common/Source/src/test/java/dev/brighten/antivpn/database/MySqlDatabaseIntegrationTest.java @@ -1,45 +1,49 @@ package dev.brighten.antivpn.database; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; + import dev.brighten.antivpn.database.sql.MySqlVPN; +import java.sql.DriverManager; import org.junit.jupiter.api.Test; import org.testcontainers.containers.MySQLContainer; import org.testcontainers.junit.jupiter.Container; import org.testcontainers.junit.jupiter.Testcontainers; -import java.sql.DriverManager; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.when; - @Testcontainers class MySqlDatabaseIntegrationTest extends DatabaseIntegrationTestSupport { - @Container - private static final MySQLContainer MYSQL = new MySQLContainer<>("mysql:8.0.36") - .withDatabaseName("antivpn") - .withUsername("testuser") - .withPassword("testpass"); + @Container + private static final MySQLContainer MYSQL = + new MySQLContainer<>("mysql:8.0.36") + .withDatabaseName("antivpn") + .withUsername("testuser") + .withPassword("testpass"); - @Test - void mysqlDatabaseImplementsTheVpnDatabaseContract() throws Exception { - assertTrue(MYSQL.isRunning(), "MySQL Testcontainer should be running"); + @Test + void mysqlDatabaseImplementsTheVpnDatabaseContract() throws Exception { + assertTrue(MYSQL.isRunning(), "MySQL Testcontainer should be running"); - try (var connection = DriverManager.getConnection(MYSQL.getJdbcUrl(), MYSQL.getUsername(), MYSQL.getPassword()); - var statement = connection.createStatement(); - var resultSet = statement.executeQuery("SELECT 1")) { - assertTrue(resultSet.next(), "Expected a row from the MySQL container"); - assertEquals(1, resultSet.getInt(1), "Expected MySQL container to respond to SELECT 1"); - } - - var pingResult = MYSQL.execInContainer("mysqladmin", "ping", "-h", "127.0.0.1", "-ptestpass"); - assertEquals(0, pingResult.getExitCode(), "Expected mysqladmin ping to succeed inside the container"); - - when(vpnConfig.getIp()).thenReturn(MYSQL.getHost()); - when(vpnConfig.getPort()).thenReturn(MYSQL.getMappedPort(3306)); - when(vpnConfig.getDatabaseName()).thenReturn(MYSQL.getDatabaseName()); - when(vpnConfig.getUsername()).thenReturn(MYSQL.getUsername()); - when(vpnConfig.getPassword()).thenReturn(MYSQL.getPassword()); - - assertDatabaseContract(new MySqlVPN()); + try (var connection = + DriverManager.getConnection( + MYSQL.getJdbcUrl(), MYSQL.getUsername(), MYSQL.getPassword()); + var statement = connection.createStatement(); + var resultSet = statement.executeQuery("SELECT 1")) { + assertTrue(resultSet.next(), "Expected a row from the MySQL container"); + assertEquals(1, resultSet.getInt(1), "Expected MySQL container to respond to SELECT 1"); } + + var pingResult = MYSQL.execInContainer("mysqladmin", "ping", "-h", "127.0.0.1", "-ptestpass"); + assertEquals( + 0, pingResult.getExitCode(), "Expected mysqladmin ping to succeed inside the container"); + + when(vpnConfig.getIp()).thenReturn(MYSQL.getHost()); + when(vpnConfig.getPort()).thenReturn(MYSQL.getMappedPort(3306)); + when(vpnConfig.getDatabaseName()).thenReturn(MYSQL.getDatabaseName()); + when(vpnConfig.getUsername()).thenReturn(MYSQL.getUsername()); + when(vpnConfig.getPassword()).thenReturn(MYSQL.getPassword()); + + assertDatabaseContract(new MySqlVPN()); + } } diff --git a/Common/Source/src/test/java/dev/brighten/antivpn/utils/AllowlistCommandTest.java b/Common/Source/src/test/java/dev/brighten/antivpn/utils/AllowlistCommandTest.java index 44e7541..3ef54ad 100644 --- a/Common/Source/src/test/java/dev/brighten/antivpn/utils/AllowlistCommandTest.java +++ b/Common/Source/src/test/java/dev/brighten/antivpn/utils/AllowlistCommandTest.java @@ -1,5 +1,12 @@ package dev.brighten.antivpn.utils; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; import dev.brighten.antivpn.AntiVPN; @@ -9,12 +16,6 @@ import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.command.impl.AllowlistCommand; import dev.brighten.antivpn.database.VPNDatabase; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Field; @@ -25,147 +26,143 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; class AllowlistCommandTest { - private static final UUID FUNKEMUNKY_UUID = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); + private static final UUID FUNKEMUNKY_UUID = + UUID.fromString("123e4567-e89b-12d3-a456-426614174000"); - @Mock - private AntiVPN antiVPN; + @Mock private AntiVPN antiVPN; - @Mock - private VPNConfig vpnConfig; + @Mock private VPNConfig vpnConfig; - @Mock - private VPNDatabase database; + @Mock private VPNDatabase database; - @Mock - private dev.brighten.antivpn.api.PlayerExecutor playerExecutor; + @Mock private dev.brighten.antivpn.api.PlayerExecutor playerExecutor; - @Mock - private CommandExecutor commandExecutor; + @Mock private CommandExecutor commandExecutor; - private TestVPNExecutor vpnExecutor; - private AutoCloseable mocks; - private AllowlistCommand command; + private TestVPNExecutor vpnExecutor; + private AutoCloseable mocks; + private AllowlistCommand command; - @BeforeEach - void setUp() throws Exception { - mocks = MockitoAnnotations.openMocks(this); - vpnExecutor = new TestVPNExecutor(); - command = new AllowlistCommand(); + @BeforeEach + void setUp() throws Exception { + mocks = MockitoAnnotations.openMocks(this); + vpnExecutor = new TestVPNExecutor(); + command = new AllowlistCommand(); - when(antiVPN.getVpnConfig()).thenReturn(vpnConfig); - when(antiVPN.getExecutor()).thenReturn(vpnExecutor); - when(antiVPN.getDatabase()).thenReturn(database); - when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor); + when(antiVPN.getVpnConfig()).thenReturn(vpnConfig); + when(antiVPN.getExecutor()).thenReturn(vpnExecutor); + when(antiVPN.getDatabase()).thenReturn(database); + when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor); - when(vpnConfig.isDatabaseEnabled()).thenReturn(true); + when(vpnConfig.isDatabaseEnabled()).thenReturn(true); - setAntiVpnInstance(antiVPN); + setAntiVpnInstance(antiVPN); + } + + @AfterEach + void tearDown() throws Exception { + MiscUtils.resetLookupEndpointsForTesting(); + setAntiVpnInstance(null); + + if (vpnExecutor != null) { + vpnExecutor.getThreadExecutor().shutdownNow(); } - @AfterEach - void tearDown() throws Exception { - MiscUtils.resetLookupEndpointsForTesting(); - setAntiVpnInstance(null); + if (mocks != null) { + mocks.close(); + } + } - if (vpnExecutor != null) { - vpnExecutor.getThreadExecutor().shutdownNow(); - } + @Test + void onlinePlayerIsWhitelistedWithoutLookup() { + APIPlayer onlinePlayer = mock(APIPlayer.class); + when(onlinePlayer.getUuid()).thenReturn(FUNKEMUNKY_UUID); + when(playerExecutor.getPlayer("funkemunky")).thenReturn(Optional.of(onlinePlayer)); - if (mocks != null) { - mocks.close(); - } + String result = command.execute(commandExecutor, new String[] {"add", "funkemunky"}); + + assertTrue(result.contains(FUNKEMUNKY_UUID.toString())); + verify(playerExecutor).getPlayer("funkemunky"); + verify(database).addWhitelist(FUNKEMUNKY_UUID); + verify(database, never()).removeWhitelist(FUNKEMUNKY_UUID); + } + + @Test + void offlinePlayerFallsBackToMojangLookupWhenPrimaryEndpointCannotBeReached() throws Exception { + when(playerExecutor.getPlayer("funkemunky")).thenReturn(Optional.empty()); + + int closedPort; + try (ServerSocket socket = new ServerSocket(0)) { + closedPort = socket.getLocalPort(); } - @Test - void onlinePlayerIsWhitelistedWithoutLookup() { - APIPlayer onlinePlayer = mock(APIPlayer.class); - when(onlinePlayer.getUuid()).thenReturn(FUNKEMUNKY_UUID); - when(playerExecutor.getPlayer("funkemunky")).thenReturn(Optional.of(onlinePlayer)); - - String result = command.execute(commandExecutor, new String[] {"add", "funkemunky"}); - - assertTrue(result.contains(FUNKEMUNKY_UUID.toString())); - verify(playerExecutor).getPlayer("funkemunky"); - verify(database).addWhitelist(FUNKEMUNKY_UUID); - verify(database, never()).removeWhitelist(FUNKEMUNKY_UUID); - } - - @Test - void offlinePlayerFallsBackToMojangLookupWhenPrimaryEndpointCannotBeReached() throws Exception { - when(playerExecutor.getPlayer("funkemunky")).thenReturn(Optional.empty()); - - int closedPort; - try (ServerSocket socket = new ServerSocket(0)) { - closedPort = socket.getLocalPort(); - } - - AtomicInteger mojangHits = new AtomicInteger(); - HttpServer mojangServer = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); - mojangServer.createContext("/users/profiles/minecraft/funkemunky", exchange -> { - mojangHits.incrementAndGet(); - respond(exchange); + AtomicInteger mojangHits = new AtomicInteger(); + HttpServer mojangServer = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0); + mojangServer.createContext( + "/users/profiles/minecraft/funkemunky", + exchange -> { + mojangHits.incrementAndGet(); + respond(exchange); }); - mojangServer.start(); + mojangServer.start(); - try { - MiscUtils.setLookupEndpointsForTesting( - "http://127.0.0.1:" + closedPort + "/mojang/uuid?name=", - "http://127.0.0.1:" + mojangServer.getAddress().getPort() + "/users/profiles/minecraft/" - ); + try { + MiscUtils.setLookupEndpointsForTesting( + "http://127.0.0.1:" + closedPort + "/mojang/uuid?name=", + "http://127.0.0.1:" + mojangServer.getAddress().getPort() + "/users/profiles/minecraft/"); - String result = command.execute(commandExecutor, new String[] {"add", "funkemunky"}); + String result = command.execute(commandExecutor, new String[] {"add", "funkemunky"}); - assertTrue(result.contains(FUNKEMUNKY_UUID.toString())); - verify(playerExecutor).getPlayer("funkemunky"); - assertEquals(1, mojangHits.get(), "Expected Mojang lookup to be used as the fallback"); - verify(database).addWhitelist(FUNKEMUNKY_UUID); - } finally { - mojangServer.stop(0); - } + assertTrue(result.contains(FUNKEMUNKY_UUID.toString())); + verify(playerExecutor).getPlayer("funkemunky"); + assertEquals(1, mojangHits.get(), "Expected Mojang lookup to be used as the fallback"); + verify(database).addWhitelist(FUNKEMUNKY_UUID); + } finally { + mojangServer.stop(0); } + } - private static void respond(HttpExchange exchange) throws IOException { - byte[] bytes = "{\"id\":\"123e4567e89b12d3a456426614174000\"}".getBytes(StandardCharsets.UTF_8); - exchange.getResponseHeaders().add("Content-Type", "application/json"); - exchange.sendResponseHeaders(200, bytes.length); - try (exchange; OutputStream outputStream = exchange.getResponseBody()) { - outputStream.write(bytes); - } + private static void respond(HttpExchange exchange) throws IOException { + byte[] bytes = "{\"id\":\"123e4567e89b12d3a456426614174000\"}".getBytes(StandardCharsets.UTF_8); + exchange.getResponseHeaders().add("Content-Type", "application/json"); + exchange.sendResponseHeaders(200, bytes.length); + try (exchange; + OutputStream outputStream = exchange.getResponseBody()) { + outputStream.write(bytes); } + } - private static void setAntiVpnInstance(AntiVPN instance) throws Exception { - Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); - instanceField.setAccessible(true); - instanceField.set(null, instance); - } + private static void setAntiVpnInstance(AntiVPN instance) throws Exception { + Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); + instanceField.setAccessible(true); + instanceField.set(null, instance); + } - protected static final class TestVPNExecutor extends VPNExecutor { - @Override - public void registerListeners() {} + protected static final class TestVPNExecutor extends VPNExecutor { + @Override + public void registerListeners() {} - @Override - public void log(Level level, String log, Object... objects) {} + @Override + public void log(Level level, String log, Object... objects) {} - @Override - public void log(String log, Object... objects) {} + @Override + public void log(String log, Object... objects) {} - @Override - public void logException(String message, Throwable ex) {} + @Override + public void logException(String message, Throwable ex) {} - @Override - public void runCommand(String command) {} + @Override + public void runCommand(String command) {} - @Override - public void disablePlugin() {} - } + @Override + public void disablePlugin() {} + } } diff --git a/Common/Source/src/test/java/dev/brighten/antivpn/utils/ReflectionUtils.java b/Common/Source/src/test/java/dev/brighten/antivpn/utils/ReflectionUtils.java new file mode 100644 index 0000000..13d4dae --- /dev/null +++ b/Common/Source/src/test/java/dev/brighten/antivpn/utils/ReflectionUtils.java @@ -0,0 +1,13 @@ +package dev.brighten.antivpn.utils; + +public class ReflectionUtils { + + public static T getDeclaredField(Class clazz, String name) + throws NoSuchFieldException, IllegalAccessException { + var declaredField = clazz.getDeclaredField(name); + + declaredField.setAccessible(true); + + return (T) declaredField.get(null); + } +} diff --git a/Common/Source/src/testFixtures/java/dev/brighten/antivpn/StandardTest.java b/Common/Source/src/testFixtures/java/dev/brighten/antivpn/StandardTest.java new file mode 100644 index 0000000..29774ee --- /dev/null +++ b/Common/Source/src/testFixtures/java/dev/brighten/antivpn/StandardTest.java @@ -0,0 +1,36 @@ +package dev.brighten.antivpn; + +import com.github.benmanes.caffeine.cache.Cache; +import dev.brighten.antivpn.api.APIPlayer; +import dev.brighten.antivpn.api.CheckResult; +import dev.brighten.antivpn.api.ResultType; +import dev.brighten.antivpn.web.objects.VPNResponse; + +public class StandardTest { + + protected void mockCache() throws NoSuchFieldException, IllegalAccessException { + mockCache( + "1.1.1.1", + new CheckResult( + VPNResponse.builder() + .success(true) + .proxy(true) + .ip("1.1.1.1") + .method("N/A") + .countryName("N/A") + .countryCode("N/A") + .city("N/A") + .build(), + ResultType.DENIED_PROXY, + true)); + } + + protected void mockCache(String ip, CheckResult result) + throws NoSuchFieldException, IllegalAccessException { + var field = APIPlayer.class.getDeclaredField("checkResultCache"); + field.setAccessible(true); + Cache checkResultCache = (Cache) field.get(null); + + checkResultCache.put(ip, result); + } +} diff --git a/Common/loader-utils/src/main/java/dev/brighten/antivpn/loader/JarInJarClassLoader.java b/Common/loader-utils/src/main/java/dev/brighten/antivpn/loader/JarInJarClassLoader.java index 27958bc..40e4d22 100644 --- a/Common/loader-utils/src/main/java/dev/brighten/antivpn/loader/JarInJarClassLoader.java +++ b/Common/loader-utils/src/main/java/dev/brighten/antivpn/loader/JarInJarClassLoader.java @@ -35,207 +35,211 @@ import java.util.Set; /** * Classloader that can load a jar from within another jar file. * - *

The "loader" jar contains the loading code & public API classes - * and is class-loaded by the platform.

+ *

The "loader" jar contains the loading code & public API classes and is class-loaded by the + * platform. * - *

The inner "plugin" jar contains the plugin itself and is class-loaded - * by the loading code & this classloader.

+ *

The inner "plugin" jar contains the plugin itself and is class-loaded by the loading code & + * this classloader. */ public class JarInJarClassLoader extends URLClassLoader { - private static final String[] PARENT_FIRST_PACKAGES = { - "java.", - "javax.", - "jdk.", - "sun.", - "com.sun.", - "org.w3c.", - "org.xml.", - "org.slf4j.", - "com.google.inject.", - "javax.inject.", - "com.velocitypowered.", - "org.bukkit.", - "net.md_5.bungee.", - "org.spongepowered.", - "org.bstats.", - "dev.brighten.antivpn.velocity.org.bstats.", - "dev.brighten.antivpn.loader." - }; + private static final String[] PARENT_FIRST_PACKAGES = { + "java.", + "javax.", + "jdk.", + "sun.", + "com.sun.", + "org.w3c.", + "org.xml.", + "org.slf4j.", + "com.google.inject.", + "javax.inject.", + "com.velocitypowered.", + "org.bukkit.", + "net.md_5.bungee.", + "org.spongepowered.", + "org.bstats.", + "dev.brighten.antivpn.velocity.org.bstats.", + "dev.brighten.antivpn.loader." + }; - static { - ClassLoader.registerAsParallelCapable(); + static { + ClassLoader.registerAsParallelCapable(); + } + + /** + * Creates a new jar-in-jar class loader. + * + * @param loaderClassLoader the loader plugin's classloader (setup and created by the platform) + * @param jarResourcePath the path to the jar-in-jar resource within the loader jar + * @throws LoadingException if something unexpectedly bad happens + */ + public JarInJarClassLoader(ClassLoader loaderClassLoader, String... jarResourcePath) + throws LoadingException { + super( + Arrays.stream(jarResourcePath) + .map(path -> extractJar(loaderClassLoader, path)) + .toArray(URL[]::new), + loaderClassLoader); + } + + public void addJarToClasspath(URL url) { + addURL(url); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + synchronized (getClassLoadingLock(name)) { + Class loadedClass = findLoadedClass(name); + if (loadedClass == null) { + loadedClass = + shouldLoadParentFirst(name) ? loadParentThenChild(name) : loadChildThenParent(name); + } + + if (resolve) { + resolveClass(loadedClass); + } + + return loadedClass; + } + } + + @Override + public URL getResource(String name) { + URL resource = findResource(name); + if (resource != null) { + return resource; + } + return super.getResource(name); + } + + @Override + public Enumeration getResources(String name) throws IOException { + Set resources = new LinkedHashSet<>(); + Enumeration childResources = findResources(name); + while (childResources.hasMoreElements()) { + resources.add(childResources.nextElement()); } - /** - * Creates a new jar-in-jar class loader. - * - * @param loaderClassLoader the loader plugin's classloader (setup and created by the platform) - * @param jarResourcePath the path to the jar-in-jar resource within the loader jar - * @throws LoadingException if something unexpectedly bad happens - */ - public JarInJarClassLoader(ClassLoader loaderClassLoader, String... jarResourcePath) throws LoadingException { - super(Arrays.stream(jarResourcePath) - .map(path -> extractJar(loaderClassLoader, path)) - .toArray(URL[]::new), loaderClassLoader); + Enumeration parentResources = + getParent() == null ? ClassLoader.getSystemResources(name) : getParent().getResources(name); + while (parentResources.hasMoreElements()) { + resources.add(parentResources.nextElement()); } - public void addJarToClasspath(URL url) { - addURL(url); + return Collections.enumeration(resources); + } + + private Class loadChildThenParent(String name) throws ClassNotFoundException { + try { + return findClass(name); + } catch (ClassNotFoundException ignored) { + return super.loadClass(name, false); + } + } + + private Class loadParentThenChild(String name) throws ClassNotFoundException { + try { + return super.loadClass(name, false); + } catch (ClassNotFoundException ignored) { + return findClass(name); + } + } + + private static boolean shouldLoadParentFirst(String className) { + for (String prefix : PARENT_FIRST_PACKAGES) { + if (className.startsWith(prefix)) { + return true; + } + } + return false; + } + + public void deleteJarResource() { + URL[] urls = getURLs(); + if (urls.length == 0) { + return; } - @Override - protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - synchronized (getClassLoadingLock(name)) { - Class loadedClass = findLoadedClass(name); - if (loadedClass == null) { - loadedClass = shouldLoadParentFirst(name) ? loadParentThenChild(name) : loadChildThenParent(name); - } + try { + Path path = Paths.get(urls[0].toURI()); + Files.deleteIfExists(path); + } catch (Exception e) { + // ignore + } + } - if (resolve) { - resolveClass(loadedClass); - } - - return loadedClass; - } + /** + * Creates a new plugin instance. + * + * @param bootstrapClass the name of the bootstrap plugin class + * @param loaderPluginType the type of the loader plugin, the only parameter of the bootstrap + * plugin constructor + * @param loaderPlugin the loader plugin instance + * @param the type of the loader plugin + * @return the instantiated bootstrap plugin + */ + public LoaderBootstrap instantiatePlugin( + String bootstrapClass, Class loaderPluginType, T loaderPlugin) throws LoadingException { + Class plugin; + try { + plugin = loadClass(bootstrapClass).asSubclass(LoaderBootstrap.class); + } catch (ReflectiveOperationException e) { + throw new LoadingException("Unable to load bootstrap class", e); } - @Override - public URL getResource(String name) { - URL resource = findResource(name); - if (resource != null) { - return resource; - } - return super.getResource(name); + Constructor constructor; + try { + constructor = plugin.getConstructor(loaderPluginType); + } catch (ReflectiveOperationException e) { + throw new LoadingException("Unable to get bootstrap constructor", e); } - @Override - public Enumeration getResources(String name) throws IOException { - Set resources = new LinkedHashSet<>(); - Enumeration childResources = findResources(name); - while (childResources.hasMoreElements()) { - resources.add(childResources.nextElement()); - } + try { + return constructor.newInstance(loaderPlugin); + } catch (ReflectiveOperationException e) { + throw new LoadingException("Unable to create bootstrap plugin instance", e); + } + } - Enumeration parentResources = getParent() == null - ? ClassLoader.getSystemResources(name) - : getParent().getResources(name); - while (parentResources.hasMoreElements()) { - resources.add(parentResources.nextElement()); - } - - return Collections.enumeration(resources); + /** + * Extracts the "jar-in-jar" from the loader plugin into a temporary file, then returns a URL that + * can be used by the {@link JarInJarClassLoader}. + * + * @param loaderClassLoader the classloader for the "host" loader plugin + * @param jarResourcePath the inner jar resource path + * @return a URL to the extracted file + */ + private static URL extractJar(ClassLoader loaderClassLoader, String jarResourcePath) + throws LoadingException { + // get the jar-in-jar resource + URL jarInJar = loaderClassLoader.getResource(jarResourcePath); + if (jarInJar == null) { + throw new LoadingException("Could not locate jar-in-jar"); } - private Class loadChildThenParent(String name) throws ClassNotFoundException { - try { - return findClass(name); - } catch (ClassNotFoundException ignored) { - return super.loadClass(name, false); - } + // create a temporary file + // on posix systems; by default, this is only read/writable by the process owner + Path path; + try { + path = Files.createTempFile(jarResourcePath, ".jar.tmp"); + } catch (IOException e) { + throw new LoadingException("Unable to create a temporary file", e); } - private Class loadParentThenChild(String name) throws ClassNotFoundException { - try { - return super.loadClass(name, false); - } catch (ClassNotFoundException ignored) { - return findClass(name); - } + // mark that the file should be deleted on exit + path.toFile().deleteOnExit(); + + // copy the jar-in-jar to the temporary file path + try (InputStream in = jarInJar.openStream()) { + Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new LoadingException("Unable to copy jar-in-jar to temporary path", e); } - private static boolean shouldLoadParentFirst(String className) { - for (String prefix : PARENT_FIRST_PACKAGES) { - if (className.startsWith(prefix)) { - return true; - } - } - return false; + try { + return path.toUri().toURL(); + } catch (MalformedURLException e) { + throw new LoadingException("Unable to get URL from path", e); } - - public void deleteJarResource() { - URL[] urls = getURLs(); - if (urls.length == 0) { - return; - } - - try { - Path path = Paths.get(urls[0].toURI()); - Files.deleteIfExists(path); - } catch (Exception e) { - // ignore - } - } - - /** - * Creates a new plugin instance. - * - * @param bootstrapClass the name of the bootstrap plugin class - * @param loaderPluginType the type of the loader plugin, the only parameter of the bootstrap - * plugin constructor - * @param loaderPlugin the loader plugin instance - * @param the type of the loader plugin - * @return the instantiated bootstrap plugin - */ - public LoaderBootstrap instantiatePlugin(String bootstrapClass, Class loaderPluginType, T loaderPlugin) throws LoadingException { - Class plugin; - try { - plugin = loadClass(bootstrapClass).asSubclass(LoaderBootstrap.class); - } catch (ReflectiveOperationException e) { - throw new LoadingException("Unable to load bootstrap class", e); - } - - Constructor constructor; - try { - constructor = plugin.getConstructor(loaderPluginType); - } catch (ReflectiveOperationException e) { - throw new LoadingException("Unable to get bootstrap constructor", e); - } - - try { - return constructor.newInstance(loaderPlugin); - } catch (ReflectiveOperationException e) { - throw new LoadingException("Unable to create bootstrap plugin instance", e); - } - } - - /** - * Extracts the "jar-in-jar" from the loader plugin into a temporary file, - * then returns a URL that can be used by the {@link JarInJarClassLoader}. - * - * @param loaderClassLoader the classloader for the "host" loader plugin - * @param jarResourcePath the inner jar resource path - * @return a URL to the extracted file - */ - private static URL extractJar(ClassLoader loaderClassLoader, String jarResourcePath) throws LoadingException { - // get the jar-in-jar resource - URL jarInJar = loaderClassLoader.getResource(jarResourcePath); - if (jarInJar == null) { - throw new LoadingException("Could not locate jar-in-jar"); - } - - // create a temporary file - // on posix systems; by default, this is only read/writable by the process owner - Path path; - try { - path = Files.createTempFile(jarResourcePath, ".jar.tmp"); - } catch (IOException e) { - throw new LoadingException("Unable to create a temporary file", e); - } - - // mark that the file should be deleted on exit - path.toFile().deleteOnExit(); - - // copy the jar-in-jar to the temporary file path - try (InputStream in = jarInJar.openStream()) { - Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - throw new LoadingException("Unable to copy jar-in-jar to temporary path", e); - } - - try { - return path.toUri().toURL(); - } catch (MalformedURLException e) { - throw new LoadingException("Unable to get URL from path", e); - } - } - + } } diff --git a/Common/loader-utils/src/main/java/dev/brighten/antivpn/loader/LoaderBootstrap.java b/Common/loader-utils/src/main/java/dev/brighten/antivpn/loader/LoaderBootstrap.java index 38e669c..bbf3247 100644 --- a/Common/loader-utils/src/main/java/dev/brighten/antivpn/loader/LoaderBootstrap.java +++ b/Common/loader-utils/src/main/java/dev/brighten/antivpn/loader/LoaderBootstrap.java @@ -18,15 +18,12 @@ package dev.brighten.antivpn.loader; import java.io.File; -/** - * Minimal bootstrap plugin, called by the loader plugin. - */ +/** Minimal bootstrap plugin, called by the loader plugin. */ public interface LoaderBootstrap { - void onLoad(File dataFolder); + void onLoad(File dataFolder); - default void onEnable() {} - - default void onDisable() {} + default void onEnable() {} + default void onDisable() {} } diff --git a/Common/loader-utils/src/main/java/dev/brighten/antivpn/loader/LoadingException.java b/Common/loader-utils/src/main/java/dev/brighten/antivpn/loader/LoadingException.java index 85f69ac..dcd8374 100644 --- a/Common/loader-utils/src/main/java/dev/brighten/antivpn/loader/LoadingException.java +++ b/Common/loader-utils/src/main/java/dev/brighten/antivpn/loader/LoadingException.java @@ -16,17 +16,14 @@ package dev.brighten.antivpn.loader; -/** - * Runtime exception used if there is a problem during loading - */ +/** Runtime exception used if there is a problem during loading */ public class LoadingException extends RuntimeException { - public LoadingException(String message) { - super(message); - } - - public LoadingException(String message, Throwable cause) { - super(message, cause); - } + public LoadingException(String message) { + super(message); + } + public LoadingException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/Sponge/SpongeLoader/build.gradle b/Sponge/SpongeLoader/build.gradle index f9d5234..c85c4f6 100644 --- a/Sponge/SpongeLoader/build.gradle +++ b/Sponge/SpongeLoader/build.gradle @@ -23,4 +23,8 @@ tasks.named('shadowJar') { dependsOn(':Sponge:SpongePlugin:shadowJar') } +tasks.named('compileJava') { + dependsOn(':Sponge:SpongePlugin:shadowJar') +} + tasks.build.dependsOn shadowJar diff --git a/Sponge/SpongeLoader/src/main/java/dev/brighten/antivpn/sponge/SpongeLoaderPlugin.java b/Sponge/SpongeLoader/src/main/java/dev/brighten/antivpn/sponge/SpongeLoaderPlugin.java index 00ec663..e2a96d9 100644 --- a/Sponge/SpongeLoader/src/main/java/dev/brighten/antivpn/sponge/SpongeLoaderPlugin.java +++ b/Sponge/SpongeLoader/src/main/java/dev/brighten/antivpn/sponge/SpongeLoaderPlugin.java @@ -19,6 +19,7 @@ package dev.brighten.antivpn.sponge; import com.google.inject.Inject; import dev.brighten.antivpn.loader.JarInJarClassLoader; import dev.brighten.antivpn.loader.LoaderBootstrap; +import java.util.Map; import org.apache.logging.log4j.Logger; import org.spongepowered.api.Server; import org.spongepowered.api.Sponge; @@ -29,43 +30,39 @@ import org.spongepowered.api.event.lifecycle.StoppingEngineEvent; import org.spongepowered.plugin.PluginContainer; import org.spongepowered.plugin.builtin.jvm.Plugin; -import java.util.Map; - @Plugin("kaurivpn") public class SpongeLoaderPlugin { - private static final String JAR_NAME = "antivpn-sponge.jarinjar"; - private static final String SOURCE_NAME = "antivpn-source.jarinjar"; - private static final String BOOTSTRAP_CLASS = "dev.brighten.antivpn.bungee.BungeePlugin"; + private static final String JAR_NAME = "antivpn-sponge.jarinjar"; + private static final String SOURCE_NAME = "antivpn-source.jarinjar"; + private static final String BOOTSTRAP_CLASS = "dev.brighten.antivpn.bungee.BungeePlugin"; - private final LoaderBootstrap plugin; + private final LoaderBootstrap plugin; - @Inject - private PluginContainer container; - @Inject - private Logger logger; + @Inject private PluginContainer container; + @Inject private Logger logger; - public SpongeLoaderPlugin() { - Map, Object> instances = Map.of(PluginContainer.class, container, Logger.class, logger); - JarInJarClassLoader loader = new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME); - this.plugin = loader.instantiatePlugin(BOOTSTRAP_CLASS, Map.class, instances); + public SpongeLoaderPlugin() { + Map, Object> instances = + Map.of(PluginContainer.class, container, Logger.class, logger); + JarInJarClassLoader loader = + new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME); + this.plugin = loader.instantiatePlugin(BOOTSTRAP_CLASS, Map.class, instances); - ConfigManager configManager = Sponge.configManager(); + ConfigManager configManager = Sponge.configManager(); - var path = configManager.sharedConfig(container).directory(); + var path = configManager.sharedConfig(container).directory(); - this.plugin.onLoad(path.toFile()); - } - - @Listener - public void onConstruct(final ConstructPluginEvent event) { - this.plugin.onEnable(); - } - - @Listener - public void onServer(final StoppingEngineEvent event) { - this.plugin.onDisable(); - } + this.plugin.onLoad(path.toFile()); + } + @Listener + public void onConstruct(final ConstructPluginEvent event) { + this.plugin.onEnable(); + } + @Listener + public void onServer(final StoppingEngineEvent event) { + this.plugin.onDisable(); + } } diff --git a/Sponge/SpongePlugin/build.gradle b/Sponge/SpongePlugin/build.gradle index 23bc9ca..04ecbe4 100644 --- a/Sponge/SpongePlugin/build.gradle +++ b/Sponge/SpongePlugin/build.gradle @@ -14,6 +14,7 @@ dependencies { testImplementation 'org.mockito:mockito-subclass:5.11.0' testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0' testImplementation 'com.github.ben-manes.caffeine:caffeine:3.1.8' + testImplementation testFixtures(project(':Common:Source')) } tasks.compileJava.dependsOn(':Common:Source:jar') diff --git a/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongeListener.java b/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongeListener.java index 7f94f34..571bd26 100644 --- a/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongeListener.java +++ b/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongeListener.java @@ -19,6 +19,8 @@ package dev.brighten.antivpn.sponge; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.api.*; import dev.brighten.antivpn.utils.StringUtil; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; import net.kyori.adventure.text.Component; import org.spongepowered.api.Sponge; import org.spongepowered.api.command.exception.CommandException; @@ -26,82 +28,92 @@ import org.spongepowered.api.event.Listener; import org.spongepowered.api.event.Order; import org.spongepowered.api.event.network.ServerSideConnectionEvent; -import java.util.concurrent.atomic.AtomicReference; -import java.util.logging.Level; - public class SpongeListener extends VPNExecutor { - @Listener(order = Order.EARLY) - public void onJoin(ServerSideConnectionEvent.Login event) { - AtomicReference player = new AtomicReference<>(AntiVPN.getInstance().getPlayerExecutor() + @Listener(order = Order.EARLY) + public void onJoin(ServerSideConnectionEvent.Login event) { + AtomicReference player = + new AtomicReference<>( + AntiVPN.getInstance() + .getPlayerExecutor() .getPlayer(event.profile().uuid()) - .orElse(new OfflinePlayer( + .orElse( + new OfflinePlayer( event.profile().uuid(), event.profile().name().orElse("Unknown"), - event.connection().address().getAddress() - ))); + event.connection().address().getAddress()))); - CheckResult result = player.get().checkPlayer(); + CheckResult result = player.get().checkPlayer(); - if(!result.resultType().isShouldBlock()) return; + if (!result.resultType().isShouldBlock()) return; - if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { - return; - } - - event.setCancelled(true); - event.setMessage(Component.text(switch (result.resultType()) { - case DENIED_PROXY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() - .getKickMessage(), player.get(), result.response()); - case DENIED_COUNTRY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() - .getCountryVanillaKickReason(), player.get(), result.response()); - default -> "You were kicked by KauriVPN for an unknown reason!"; - })); + if (!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { + return; } - @Listener - public void onPlayerDisconnect(ServerSideConnectionEvent.Disconnect event) { - event.profile().ifPresent(profile -> - AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(profile.uuid())); - } + event.setCancelled(true); + event.setMessage( + Component.text( + switch (result.resultType()) { + case DENIED_PROXY -> + StringUtil.varReplace( + AntiVPN.getInstance().getVpnConfig().getKickMessage(), + player.get(), + result.response()); + case DENIED_COUNTRY -> + StringUtil.varReplace( + AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(), + player.get(), + result.response()); + default -> "You were kicked by KauriVPN for an unknown reason!"; + })); + } - @Override - public void registerListeners() { - Sponge.eventManager().registerListeners(SpongePlugin.getInstance().getContainer(), this); - } + @Listener + public void onPlayerDisconnect(ServerSideConnectionEvent.Disconnect event) { + event + .profile() + .ifPresent( + profile -> AntiVPN.getInstance().getPlayerExecutor().unloadPlayer(profile.uuid())); + } - @Override - public void log(Level level, String log, Object... objects) { - if (level.equals(Level.SEVERE)) { - SpongePlugin.getInstance().getLogger().error(String.format(log, objects)); - } else if (level.equals(Level.WARNING)) { - SpongePlugin.getInstance().getLogger().warn(String.format(log, objects)); - } else { - SpongePlugin.getInstance().getLogger().info(String.format(log, objects)); - } - } + @Override + public void registerListeners() { + Sponge.eventManager().registerListeners(SpongePlugin.getInstance().getContainer(), this); + } - @Override - public void log(String log, Object... objects) { - log(Level.INFO, log, objects); + @Override + public void log(Level level, String log, Object... objects) { + if (level.equals(Level.SEVERE)) { + SpongePlugin.getInstance().getLogger().error(String.format(log, objects)); + } else if (level.equals(Level.WARNING)) { + SpongePlugin.getInstance().getLogger().warn(String.format(log, objects)); + } else { + SpongePlugin.getInstance().getLogger().info(String.format(log, objects)); } + } - @Override - public void logException(String message, Throwable ex) { - SpongePlugin.getInstance().getLogger().error(message, ex); - } + @Override + public void log(String log, Object... objects) { + log(Level.INFO, log, objects); + } - @Override - public void runCommand(String command) { - try { - Sponge.server().commandManager().process(Sponge.systemSubject(), command); - } catch (CommandException e) { - logException(e); - } - } + @Override + public void logException(String message, Throwable ex) { + SpongePlugin.getInstance().getLogger().error(message, ex); + } - @Override - public void disablePlugin() { - AntiVPN.getInstance().getExecutor().log(Level.INFO, "Disabling listeners for plugin..."); + @Override + public void runCommand(String command) { + try { + Sponge.server().commandManager().process(Sponge.systemSubject(), command); + } catch (CommandException e) { + logException(e); } + } + + @Override + public void disablePlugin() { + AntiVPN.getInstance().getExecutor().log(Level.INFO, "Disabling listeners for plugin..."); + } } diff --git a/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongePlayer.java b/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongePlayer.java index 595bc4d..2b044ed 100644 --- a/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongePlayer.java +++ b/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongePlayer.java @@ -23,25 +23,25 @@ import org.spongepowered.api.entity.living.player.server.ServerPlayer; public class SpongePlayer extends APIPlayer { - private final ServerPlayer player; + private final ServerPlayer player; - public SpongePlayer(ServerPlayer player) { - super(player.uniqueId(), player.name(), player.connection().address().getAddress()); - this.player = player; - } + public SpongePlayer(ServerPlayer player) { + super(player.uniqueId(), player.name(), player.connection().address().getAddress()); + this.player = player; + } - @Override - public void sendMessage(String message) { - player.sendMessage(Component.text(StringUtil.translateColorCodes('&', message))); - } + @Override + public void sendMessage(String message) { + player.sendMessage(Component.text(StringUtil.translateColorCodes('&', message))); + } - @Override - public void kickPlayer(String reason) { - player.kick(Component.text(StringUtil.translateColorCodes('&', reason))); - } + @Override + public void kickPlayer(String reason) { + player.kick(Component.text(StringUtil.translateColorCodes('&', reason))); + } - @Override - public boolean hasPermission(String permission) { - return player.hasPermission(permission); - } + @Override + public boolean hasPermission(String permission) { + return player.hasPermission(permission); + } } diff --git a/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongePlayerExecutor.java b/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongePlayerExecutor.java index 6ac58f1..e40f650 100644 --- a/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongePlayerExecutor.java +++ b/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongePlayerExecutor.java @@ -20,67 +20,65 @@ import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.PlayerExecutor; -import org.spongepowered.api.Sponge; -import org.spongepowered.api.entity.living.player.server.ServerPlayer; - import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; +import org.spongepowered.api.Sponge; +import org.spongepowered.api.entity.living.player.server.ServerPlayer; public class SpongePlayerExecutor implements PlayerExecutor { - private final Cache playerCache = Caffeine.newBuilder().maximumSize(10000) - .expireAfterAccess(30, TimeUnit.MINUTES) - .build(); + private final Cache playerCache = + Caffeine.newBuilder().maximumSize(10000).expireAfterAccess(30, TimeUnit.MINUTES).build(); - @Override - public Optional getPlayer(String name) { - Optional serverPlayer = Sponge.server().player(name); + @Override + public Optional getPlayer(String name) { + Optional serverPlayer = Sponge.server().player(name); - return serverPlayer.map(SpongePlayer::new); + return serverPlayer.map(SpongePlayer::new); + } + + @Override + public Optional getPlayer(UUID uuid) { + SpongePlayer cachedPlayer = playerCache.getIfPresent(uuid); + + if (cachedPlayer != null) { + return Optional.of(cachedPlayer); } - @Override - public Optional getPlayer(UUID uuid) { - SpongePlayer cachedPlayer = playerCache.getIfPresent(uuid); + Optional serverPlayer = Sponge.server().player(uuid); - if(cachedPlayer != null) { - return Optional.of(cachedPlayer); - } + Optional player = serverPlayer.map(SpongePlayer::new); - Optional serverPlayer = Sponge.server().player(uuid); + player.ifPresent(value -> playerCache.put(uuid, (SpongePlayer) value)); - Optional player = serverPlayer.map(SpongePlayer::new); + return player; + } - player.ifPresent(value -> playerCache.put(uuid, (SpongePlayer) value)); + @Override + public void unloadPlayer(UUID uuid) { + playerCache.invalidate(uuid); + } - return player; - } + @Override + public List getOnlinePlayers() { + if (!Sponge.game().isServerAvailable()) return Collections.emptyList(); + return Sponge.server().onlinePlayers().stream() + .map( + pl -> { + SpongePlayer cachedPlayer = playerCache.getIfPresent(pl.uniqueId()); - @Override - public void unloadPlayer(UUID uuid) { - playerCache.invalidate(uuid); - } + if (cachedPlayer != null) { + return cachedPlayer; + } - @Override - public List getOnlinePlayers() { - if(!Sponge.game().isServerAvailable()) return Collections.emptyList(); - return Sponge.server().onlinePlayers() - .stream() - .map(pl -> { - SpongePlayer cachedPlayer = playerCache.getIfPresent(pl.uniqueId()); + SpongePlayer player = new SpongePlayer(pl); + playerCache.put(pl.uniqueId(), player); - if(cachedPlayer != null) { - return cachedPlayer; - } - - SpongePlayer player = new SpongePlayer(pl); - playerCache.put(pl.uniqueId(), player); - - return (APIPlayer) player; - }) - .toList(); - } + return (APIPlayer) player; + }) + .toList(); + } } diff --git a/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongePlugin.java b/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongePlugin.java index 812c498..b836efc 100644 --- a/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongePlugin.java +++ b/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/SpongePlugin.java @@ -19,6 +19,8 @@ package dev.brighten.antivpn.sponge; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.loader.LoaderBootstrap; import dev.brighten.antivpn.sponge.command.SpongeCommand; +import java.io.File; +import java.util.Map; import lombok.Getter; import org.apache.logging.log4j.Logger; import org.spongepowered.api.Sponge; @@ -27,54 +29,49 @@ import org.spongepowered.api.event.Listener; import org.spongepowered.api.event.lifecycle.*; import org.spongepowered.plugin.PluginContainer; -import java.io.File; -import java.util.Map; - @Getter public class SpongePlugin implements LoaderBootstrap { - //Plugin init - @Getter - private static SpongePlugin instance; + // Plugin init + @Getter private static SpongePlugin instance; - private Logger logger; - private PluginContainer container; - private final Map, Object> objects; - private File dataFolder; + private Logger logger; + private PluginContainer container; + private final Map, Object> objects; + private File dataFolder; + public SpongePlugin(Map, Object> objects) { + this.objects = objects; + } - public SpongePlugin(Map, Object> objects) { - this.objects = objects; + @Listener + public void onRegisterRawCommands(final RegisterCommandEvent event) { + AntiVPN.getInstance().getExecutor().log("Registering commands..."); + for (dev.brighten.antivpn.command.Command command : AntiVPN.getInstance().getCommands()) { + AntiVPN.getInstance().getExecutor().log("Registering command %s...", command.name()); + event.register(this.container, new SpongeCommand(command), command.name(), command.aliases()); } + } - @Listener - public void onRegisterRawCommands(final RegisterCommandEvent event){ - AntiVPN.getInstance().getExecutor().log("Registering commands..."); - for (dev.brighten.antivpn.command.Command command : AntiVPN.getInstance().getCommands()) { - AntiVPN.getInstance().getExecutor().log("Registering command %s...", command.name()); - event.register(this.container, new SpongeCommand(command), command.name(), command.aliases()); - } - } + @Override + public void onLoad(File dataFolder) { + this.dataFolder = dataFolder; + container = (PluginContainer) objects.get(PluginContainer.class); + logger = (Logger) objects.get(Logger.class); + Sponge.eventManager().registerListeners(this.container, this); + } - @Override - public void onLoad(File dataFolder) { - this.dataFolder = dataFolder; - container = (PluginContainer) objects.get(PluginContainer.class); - logger = (Logger) objects.get(Logger.class); - Sponge.eventManager().registerListeners(this.container, this); - } + @Override + public void onEnable() { + instance = this; - @Override - public void onEnable() { - instance = this; + SpongeListener spongeListener = new SpongeListener(); - SpongeListener spongeListener = new SpongeListener(); + AntiVPN.start(spongeListener, new SpongePlayerExecutor(), dataFolder); + } - AntiVPN.start(spongeListener, new SpongePlayerExecutor(), dataFolder); - } - - @Override - public void onDisable() { - AntiVPN.getInstance().getExecutor().disablePlugin(); - } + @Override + public void onDisable() { + AntiVPN.getInstance().getExecutor().disablePlugin(); + } } diff --git a/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/command/SpongeCommand.java b/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/command/SpongeCommand.java index 281e318..0ec3bc3 100644 --- a/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/command/SpongeCommand.java +++ b/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/command/SpongeCommand.java @@ -20,6 +20,8 @@ import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.command.Command; import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.utils.StringUtil; +import java.util.*; +import java.util.stream.IntStream; import lombok.val; import net.kyori.adventure.text.Component; import org.checkerframework.checker.nullness.qual.NonNull; @@ -28,100 +30,114 @@ import org.spongepowered.api.command.CommandCompletion; import org.spongepowered.api.command.CommandResult; import org.spongepowered.api.command.parameter.ArgumentReader; -import java.util.*; -import java.util.stream.IntStream; - public class SpongeCommand implements org.spongepowered.api.command.Command.Raw { - private final Command command; + private final Command command; - public SpongeCommand(Command command) { - this.command = command; - } + public SpongeCommand(Command command) { + this.command = command; + } - @Override - public CommandResult process(CommandCause sender, ArgumentReader.Mutable arguments) { + @Override + public CommandResult process(CommandCause sender, ArgumentReader.Mutable arguments) { - String[] args = arguments.input().split(" "); + String[] args = arguments.input().split(" "); - CommandExecutor commandExecutor = new SpongeCommandExecutor(sender); + CommandExecutor commandExecutor = new SpongeCommandExecutor(sender); - val children = command.children(); + val children = command.children(); - if(children.length > 0 && args.length > 0) { - for (Command child : children) { - if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases()) - .anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) { - if(!sender.hasPermission("antivpn.command.*") - && !sender.hasPermission(child.permission())) { - return CommandResult.error(Component.text(StringUtil.translateAlternateColorCodes('&', - AntiVPN.getInstance().getMessageHandler().getString("no-permission").getMessage()))); - } + if (children.length > 0 && args.length > 0) { + for (Command child : children) { + if (child.name().equalsIgnoreCase(args[0]) + || Arrays.stream(child.aliases()).anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) { + if (!sender.hasPermission("antivpn.command.*") + && !sender.hasPermission(child.permission())) { + return CommandResult.error( + Component.text( + StringUtil.translateAlternateColorCodes( + '&', + AntiVPN.getInstance() + .getMessageHandler() + .getString("no-permission") + .getMessage()))); + } - commandExecutor.sendMessage(StringUtil - .translateAlternateColorCodes('&', - child.execute(commandExecutor, IntStream - .range(0, args.length - 1) - .mapToObj(i -> args[i + 1]).toArray(String[]::new)))); - return CommandResult.success(); - } - } + commandExecutor.sendMessage( + StringUtil.translateAlternateColorCodes( + '&', + child.execute( + commandExecutor, + IntStream.range(0, args.length - 1) + .mapToObj(i -> args[i + 1]) + .toArray(String[]::new)))); + return CommandResult.success(); } - - commandExecutor.sendMessage(StringUtil - .translateAlternateColorCodes('&', - command.execute(new SpongeCommandExecutor(sender), args))); - - command.execute(new SpongeCommandExecutor(sender), args); - return CommandResult.success(); + } } - @Override - public List complete(CommandCause sender, ArgumentReader.Mutable arguments) { - val children = command.children(); - String[] args = arguments.input().split(" "); - if(children.length > 0 && args.length > 0) { - for (Command child : children) { - if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases()) - .anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) { - return child.tabComplete(new SpongeCommandExecutor(sender), "alias", IntStream - .range(0, args.length - 1) - .mapToObj(i -> args[i + 1]).toArray(String[]::new)) - .stream() - .map(CommandCompletion::of) - .toList(); - } - } + commandExecutor.sendMessage( + StringUtil.translateAlternateColorCodes( + '&', command.execute(new SpongeCommandExecutor(sender), args))); + + command.execute(new SpongeCommandExecutor(sender), args); + return CommandResult.success(); + } + + @Override + public List complete(CommandCause sender, ArgumentReader.Mutable arguments) { + val children = command.children(); + String[] args = arguments.input().split(" "); + if (children.length > 0 && args.length > 0) { + for (Command child : children) { + if (child.name().equalsIgnoreCase(args[0]) + || Arrays.stream(child.aliases()) + .anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) { + return child + .tabComplete( + new SpongeCommandExecutor(sender), + "alias", + IntStream.range(0, args.length - 1) + .mapToObj(i -> args[i + 1]) + .toArray(String[]::new)) + .stream() + .map(CommandCompletion::of) + .toList(); } - return command.tabComplete(new SpongeCommandExecutor(sender), "alias", args) - .stream() - .map(CommandCompletion::of) - .toList(); + } } + return command.tabComplete(new SpongeCommandExecutor(sender), "alias", args).stream() + .map(CommandCompletion::of) + .toList(); + } - @Override - public boolean canExecute(CommandCause cause) { - return cause.hasPermission(command.permission()); - } + @Override + public boolean canExecute(CommandCause cause) { + return cause.hasPermission(command.permission()); + } - @Override - public Optional shortDescription(CommandCause cause) { - return command.description() != null ? Optional.of(Component.text(command.description())) : Optional.empty(); - } + @Override + public Optional shortDescription(CommandCause cause) { + return command.description() != null + ? Optional.of(Component.text(command.description())) + : Optional.empty(); + } - @Override - public Optional extendedDescription(CommandCause cause) { - return Optional.empty(); - } + @Override + public Optional extendedDescription(CommandCause cause) { + return Optional.empty(); + } - @Override - public Optional help(@NonNull CommandCause cause) { - return Optional.of(Component.text(StringUtil.translateAlternateColorCodes('&', - command.execute(new SpongeCommandExecutor(cause), new String[0])))); - } + @Override + public Optional help(@NonNull CommandCause cause) { + return Optional.of( + Component.text( + StringUtil.translateAlternateColorCodes( + '&', command.execute(new SpongeCommandExecutor(cause), new String[0])))); + } - @Override - public Component usage(CommandCause cause) { - return command.usage() != null ? Component.text(command.usage()) : Component.empty(); - } + @Override + public Component usage(CommandCause cause) { + return command.usage() != null ? Component.text(command.usage()) : Component.empty(); + } } diff --git a/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/command/SpongeCommandExecutor.java b/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/command/SpongeCommandExecutor.java index 8ccaccb..cb6f809 100644 --- a/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/command/SpongeCommandExecutor.java +++ b/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/command/SpongeCommandExecutor.java @@ -20,39 +20,38 @@ import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.command.CommandExecutor; import dev.brighten.antivpn.sponge.util.StringUtil; +import java.util.Optional; import lombok.RequiredArgsConstructor; import net.kyori.adventure.text.Component; import org.spongepowered.api.command.CommandCause; import org.spongepowered.api.entity.living.player.server.ServerPlayer; -import java.util.Optional; - @RequiredArgsConstructor public class SpongeCommandExecutor implements CommandExecutor { - private final CommandCause cause; + private final CommandCause cause; - @Override - public void sendMessage(String message, Object... objects) { - cause.sendMessage(Component.text(StringUtil.translateColorCodes('&', - String.format(message, objects)))); - } + @Override + public void sendMessage(String message, Object... objects) { + cause.sendMessage( + Component.text(StringUtil.translateColorCodes('&', String.format(message, objects)))); + } - @Override - public boolean hasPermission(String permission) { - return cause.hasPermission(permission); - } + @Override + public boolean hasPermission(String permission) { + return cause.hasPermission(permission); + } - @Override - public Optional getPlayer() { - if(cause.subject() instanceof ServerPlayer serverPlayer) { - return AntiVPN.getInstance().getPlayerExecutor().getPlayer(serverPlayer.uniqueId()); - } - return Optional.empty(); + @Override + public Optional getPlayer() { + if (cause.subject() instanceof ServerPlayer serverPlayer) { + return AntiVPN.getInstance().getPlayerExecutor().getPlayer(serverPlayer.uniqueId()); } + return Optional.empty(); + } - @Override - public boolean isPlayer() { - return cause.subject() instanceof ServerPlayer; - } + @Override + public boolean isPlayer() { + return cause.subject() instanceof ServerPlayer; + } } diff --git a/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/util/StringUtil.java b/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/util/StringUtil.java index bd533c6..17873dd 100644 --- a/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/util/StringUtil.java +++ b/Sponge/SpongePlugin/src/main/java/dev/brighten/antivpn/sponge/util/StringUtil.java @@ -18,16 +18,16 @@ package dev.brighten.antivpn.sponge.util; public class StringUtil { - public static String translateColorCodes(char altColorChar, String textToTranslate) { - char[] b = textToTranslate.toCharArray(); + public static String translateColorCodes(char altColorChar, String textToTranslate) { + char[] b = textToTranslate.toCharArray(); - for(int i = 0; i < b.length - 1; ++i) { - if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i + 1]) > -1) { - b[i] = 167; - b[i + 1] = Character.toLowerCase(b[i + 1]); - } - } - - return new String(b); + for (int i = 0; i < b.length - 1; ++i) { + if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".indexOf(b[i + 1]) > -1) { + b[i] = 167; + b[i + 1] = Character.toLowerCase(b[i + 1]); + } } + + return new String(b); + } } diff --git a/Sponge/SpongePlugin/src/test/java/dev/brighten/antivpn/sponge/SpongeListenerTest.java b/Sponge/SpongePlugin/src/test/java/dev/brighten/antivpn/sponge/SpongeListenerTest.java index f90e9c4..c7be9f2 100644 --- a/Sponge/SpongePlugin/src/test/java/dev/brighten/antivpn/sponge/SpongeListenerTest.java +++ b/Sponge/SpongePlugin/src/test/java/dev/brighten/antivpn/sponge/SpongeListenerTest.java @@ -1,12 +1,20 @@ package dev.brighten.antivpn.sponge; +import static org.mockito.Mockito.*; + import dev.brighten.antivpn.AntiVPN; +import dev.brighten.antivpn.StandardTest; import dev.brighten.antivpn.api.PlayerExecutor; import dev.brighten.antivpn.api.VPNConfig; import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.message.MessageHandler; import dev.brighten.antivpn.message.VpnString; import dev.brighten.antivpn.web.objects.VPNResponse; +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -14,101 +22,97 @@ import org.spongepowered.api.event.network.ServerSideConnectionEvent; import org.spongepowered.api.network.ServerSideConnection; import org.spongepowered.api.profile.GameProfile; -import java.lang.reflect.Field; -import java.net.InetSocketAddress; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; +public class SpongeListenerTest extends StandardTest { -import static org.mockito.Mockito.*; + private SpongeListener listener; + private VPNExecutor vpnExecutor; -public class SpongeListenerTest { + @BeforeEach + public void setUp() throws Exception { + AntiVPN antiVPN = mock(AntiVPN.class); + VPNConfig config = mock(VPNConfig.class); + PlayerExecutor playerExecutor = mock(PlayerExecutor.class); + vpnExecutor = mock(VPNExecutor.class); + MessageHandler messageHandler = mock(MessageHandler.class); - private SpongeListener listener; - private VPNExecutor vpnExecutor; + when(antiVPN.getVpnConfig()).thenReturn(config); + when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor); + when(antiVPN.getExecutor()).thenReturn(vpnExecutor); + when(antiVPN.getMessageHandler()).thenReturn(messageHandler); - @BeforeEach - public void setUp() throws Exception { - AntiVPN antiVPN = mock(AntiVPN.class); - VPNConfig config = mock(VPNConfig.class); - PlayerExecutor playerExecutor = mock(PlayerExecutor.class); - vpnExecutor = mock(VPNExecutor.class); - MessageHandler messageHandler = mock(MessageHandler.class); + when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty()); + when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList()); + when(config.getCountryList()).thenReturn(java.util.Collections.emptyList()); + when(config.isKickPlayers()).thenReturn(true); + when(config.getKickMessage()).thenReturn("Blocked!"); - when(antiVPN.getVpnConfig()).thenReturn(config); - when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor); - when(antiVPN.getExecutor()).thenReturn(vpnExecutor); - when(antiVPN.getMessageHandler()).thenReturn(messageHandler); + VpnString mockVpnString = mock(VpnString.class); + when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!"); + when(messageHandler.getString(anyString())).thenReturn(mockVpnString); - when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty()); - when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList()); - when(config.getCountryList()).thenReturn(java.util.Collections.emptyList()); - when(config.isKickPlayers()).thenReturn(true); - when(config.getKickMessage()).thenReturn("Blocked!"); + when(vpnExecutor.checkIp(anyString())) + .thenReturn( + CompletableFuture.completedFuture( + VPNResponse.builder() + .success(true) + .proxy(false) + .ip("127.0.0.1") + .method("N/A") + .countryName("N/A") + .city("N/A") + .build())); - VpnString mockVpnString = mock(VpnString.class); - when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!"); - when(messageHandler.getString(anyString())).thenReturn(mockVpnString); + // Use reflection to set the private static INSTANCE field + Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); + instanceField.setAccessible(true); + instanceField.set(null, antiVPN); - when(vpnExecutor.checkIp(anyString())).thenReturn(CompletableFuture.completedFuture( - VPNResponse.builder().success(true).proxy(false).ip("127.0.0.1") - .method("N/A").countryName("N/A").city("N/A").build() - )); + listener = new SpongeListener(); + } - // Use reflection to set the private static INSTANCE field - Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); - instanceField.setAccessible(true); - instanceField.set(null, antiVPN); + @AfterEach + public void tearDown() throws Exception { + // Reset the singleton + Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); + instanceField.setAccessible(true); + instanceField.set(null, null); + } - listener = new SpongeListener(); - } + @Test + public void testLoginEventAllowed() { + ServerSideConnectionEvent.Login event = mock(ServerSideConnectionEvent.Login.class); + GameProfile profile = mock(GameProfile.class); + ServerSideConnection connection = mock(ServerSideConnection.class); - @AfterEach - public void tearDown() throws Exception { - // Reset the singleton - Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); - instanceField.setAccessible(true); - instanceField.set(null, null); - } + when(event.profile()).thenReturn(profile); + when(event.connection()).thenReturn(connection); + when(profile.uuid()).thenReturn(UUID.randomUUID()); + when(profile.name()).thenReturn(Optional.of("TestPlayer")); + when(connection.address()).thenReturn(new InetSocketAddress("127.0.0.1", 12345)); - @Test - public void testLoginEventAllowed() { - ServerSideConnectionEvent.Login event = mock(ServerSideConnectionEvent.Login.class); - GameProfile profile = mock(GameProfile.class); - ServerSideConnection connection = mock(ServerSideConnection.class); + listener.onJoin(event); - when(event.profile()).thenReturn(profile); - when(event.connection()).thenReturn(connection); - when(profile.uuid()).thenReturn(UUID.randomUUID()); - when(profile.name()).thenReturn(Optional.of("TestPlayer")); - when(connection.address()).thenReturn(new InetSocketAddress("127.0.0.1", 12345)); + verify(event, never()).setCancelled(true); + } - listener.onJoin(event); + @Test + public void testLoginEventBlocked() throws NoSuchFieldException, IllegalAccessException { + ServerSideConnectionEvent.Login event = mock(ServerSideConnectionEvent.Login.class); + GameProfile profile = mock(GameProfile.class); + ServerSideConnection connection = mock(ServerSideConnection.class); - verify(event, never()).setCancelled(true); - } + when(event.profile()).thenReturn(profile); + when(event.connection()).thenReturn(connection); + when(profile.uuid()).thenReturn(UUID.randomUUID()); + when(profile.name()).thenReturn(Optional.of("ProxyPlayer")); + when(connection.address()).thenReturn(new InetSocketAddress("1.1.1.1", 12345)); - @Test - public void testLoginEventBlocked() { - ServerSideConnectionEvent.Login event = mock(ServerSideConnectionEvent.Login.class); - GameProfile profile = mock(GameProfile.class); - ServerSideConnection connection = mock(ServerSideConnection.class); + // Mock proxy response + mockCache(); - when(event.profile()).thenReturn(profile); - when(event.connection()).thenReturn(connection); - when(profile.uuid()).thenReturn(UUID.randomUUID()); - when(profile.name()).thenReturn(Optional.of("ProxyPlayer")); - when(connection.address()).thenReturn(new InetSocketAddress("1.1.1.1", 12345)); + listener.onJoin(event); - // Mock proxy response - when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture( - VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1") - .method("N/A").countryName("N/A").countryCode("N/A").city("N/A").build() - )); - - listener.onJoin(event); - - verify(event).setCancelled(true); - verify(event).setMessage(any()); - } + verify(event).setCancelled(true); + verify(event).setMessage(any()); + } } diff --git a/Velocity/VelocityLoader/build.gradle b/Velocity/VelocityLoader/build.gradle index 1ce3f05..c893cd5 100644 --- a/Velocity/VelocityLoader/build.gradle +++ b/Velocity/VelocityLoader/build.gradle @@ -40,6 +40,10 @@ tasks.named('shadowJar') { dependsOn(':Velocity:VelocityPlugin:shadowJar') } +tasks.named('compileJava') { + dependsOn(':Velocity:VelocityPlugin:shadowJar') +} + tasks.build.dependsOn shadowJar tasks.named('runVelocity') { diff --git a/Velocity/VelocityLoader/src/main/java/dev/brighten/antivpn/velocity/VelocityPluginLoader.java b/Velocity/VelocityLoader/src/main/java/dev/brighten/antivpn/velocity/VelocityPluginLoader.java index bf9885b..06b9c9c 100644 --- a/Velocity/VelocityLoader/src/main/java/dev/brighten/antivpn/velocity/VelocityPluginLoader.java +++ b/Velocity/VelocityLoader/src/main/java/dev/brighten/antivpn/velocity/VelocityPluginLoader.java @@ -25,56 +25,60 @@ import com.velocitypowered.api.plugin.annotation.DataDirectory; import com.velocitypowered.api.proxy.ProxyServer; import dev.brighten.antivpn.loader.JarInJarClassLoader; import dev.brighten.antivpn.loader.LoaderBootstrap; -import org.bstats.velocity.Metrics; - import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; +import org.bstats.velocity.Metrics; -@Plugin(id = "kaurivpn", name = "KauriVPN", version = "1.7.1", authors = {"funkemunky"}) +@Plugin( + id = "kaurivpn", + name = "KauriVPN", + version = "1.7.1", + authors = {"funkemunky"}) public class VelocityPluginLoader { - private static final String JAR_NAME = "antivpn-velocity.jarinjar"; - private static final String SOURCE_NAME = "antivpn-source.jarinjar"; - private static final String BOOTSTRAP_CLASS = "dev.brighten.antivpn.velocity.VelocityPlugin"; + private static final String JAR_NAME = "antivpn-velocity.jarinjar"; + private static final String SOURCE_NAME = "antivpn-source.jarinjar"; + private static final String BOOTSTRAP_CLASS = "dev.brighten.antivpn.velocity.VelocityPlugin"; - private final LoaderBootstrap plugin; - private final ClassLoader pluginClassLoader; + private final LoaderBootstrap plugin; + private final ClassLoader pluginClassLoader; - @Inject - public VelocityPluginLoader(ProxyServer server, Logger logger, @DataDirectory Path path, Metrics.Factory metricsFactory) { - Map, Object> instances = new HashMap<>(); - instances.put(ProxyServer.class, server); - instances.put(Logger.class, logger); - instances.put(Path.class, path); - instances.put(String.class, metricsFactory); - instances.put(LoaderBootstrap.class, this); - JarInJarClassLoader loader = new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME); - this.pluginClassLoader = loader; - this.plugin = loader.instantiatePlugin(BOOTSTRAP_CLASS, Map.class, instances); - runWithPluginClassLoader(() -> plugin.onLoad(path.toFile())); + @Inject + public VelocityPluginLoader( + ProxyServer server, Logger logger, @DataDirectory Path path, Metrics.Factory metricsFactory) { + Map, Object> instances = new HashMap<>(); + instances.put(ProxyServer.class, server); + instances.put(Logger.class, logger); + instances.put(Path.class, path); + instances.put(String.class, metricsFactory); + instances.put(LoaderBootstrap.class, this); + JarInJarClassLoader loader = + new JarInJarClassLoader(getClass().getClassLoader(), JAR_NAME, SOURCE_NAME); + this.pluginClassLoader = loader; + this.plugin = loader.instantiatePlugin(BOOTSTRAP_CLASS, Map.class, instances); + runWithPluginClassLoader(() -> plugin.onLoad(path.toFile())); + } + + @Subscribe + public void onInit(ProxyInitializeEvent event) { + runWithPluginClassLoader(plugin::onEnable); + } + + @Subscribe + public void onDisable(ProxyShutdownEvent event) { + runWithPluginClassLoader(plugin::onDisable); + } + + private void runWithPluginClassLoader(Runnable action) { + Thread thread = Thread.currentThread(); + ClassLoader previousClassLoader = thread.getContextClassLoader(); + thread.setContextClassLoader(pluginClassLoader); + try { + action.run(); + } finally { + thread.setContextClassLoader(previousClassLoader); } - - @Subscribe - public void onInit(ProxyInitializeEvent event) { - runWithPluginClassLoader(plugin::onEnable); - } - - @Subscribe - public void onDisable(ProxyShutdownEvent event) { - runWithPluginClassLoader(plugin::onDisable); - } - - private void runWithPluginClassLoader(Runnable action) { - Thread thread = Thread.currentThread(); - ClassLoader previousClassLoader = thread.getContextClassLoader(); - thread.setContextClassLoader(pluginClassLoader); - try { - action.run(); - } finally { - thread.setContextClassLoader(previousClassLoader); - } - } - + } } diff --git a/Velocity/VelocityPlugin/build.gradle b/Velocity/VelocityPlugin/build.gradle index 1c99cfe..80266c9 100644 --- a/Velocity/VelocityPlugin/build.gradle +++ b/Velocity/VelocityPlugin/build.gradle @@ -15,6 +15,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4' testImplementation project(':Common:Source') testImplementation project(':Common:loader-utils') + testImplementation testFixtures(project(':Common:Source')) } tasks.compileJava.dependsOn(':Common:Source:jar') diff --git a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityListener.java b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityListener.java index e0c77f6..6b7f7ab 100644 --- a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityListener.java +++ b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityListener.java @@ -25,92 +25,123 @@ import dev.brighten.antivpn.api.CheckResult; import dev.brighten.antivpn.api.OfflinePlayer; import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.utils.StringUtil; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; - import java.util.logging.Level; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; public class VelocityListener extends VPNExecutor { - @Override - public void registerListeners() { - VelocityPlugin.INSTANCE.getServer().getEventManager() - .register(VelocityPlugin.INSTANCE.getPluginInstance(), this); + @Override + public void registerListeners() { + VelocityPlugin.INSTANCE + .getServer() + .getEventManager() + .register(VelocityPlugin.INSTANCE.getPluginInstance(), this); - VelocityPlugin.INSTANCE.getServer().getEventManager().register(VelocityPlugin.INSTANCE.getPluginInstance(), DisconnectEvent.class, - event -> AntiVPN.getInstance() - .getPlayerExecutor() - .unloadPlayer(event.getPlayer().getUniqueId())); + VelocityPlugin.INSTANCE + .getServer() + .getEventManager() + .register( + VelocityPlugin.INSTANCE.getPluginInstance(), + DisconnectEvent.class, + event -> + AntiVPN.getInstance() + .getPlayerExecutor() + .unloadPlayer(event.getPlayer().getUniqueId())); - VelocityPlugin.INSTANCE.getServer().getEventManager().register(VelocityPlugin.INSTANCE.getPluginInstance(), LoginEvent.class, - this::onLogin); + VelocityPlugin.INSTANCE + .getServer() + .getEventManager() + .register(VelocityPlugin.INSTANCE.getPluginInstance(), LoginEvent.class, this::onLogin); + } + + public void onLogin(LoginEvent event) { + APIPlayer player = + AntiVPN.getInstance() + .getPlayerExecutor() + .getPlayer(event.getPlayer().getUniqueId()) + .orElse( + new OfflinePlayer( + event.getPlayer().getUniqueId(), + event.getPlayer().getUsername(), + event.getPlayer().getRemoteAddress().getAddress())); + + CheckResult result = player.checkPlayer(); + + if (!result.resultType().isShouldBlock()) return; + + if (!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { + return; } - public void onLogin(LoginEvent event) { - APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getPlayer().getUniqueId()) - .orElse(new OfflinePlayer( - event.getPlayer().getUniqueId(), - event.getPlayer().getUsername(), - event.getPlayer().getRemoteAddress().getAddress() - )); - - CheckResult result = player.checkPlayer(); - - if(!result.resultType().isShouldBlock()) return; - - if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { - return; - } - - switch (result.resultType()) { - case DENIED_COUNTRY -> event.setResult(ResultedEvent.ComponentResult.denied( - LegacyComponentSerializer.builder() - .character('&') - .build().deserialize(AntiVPN.getInstance().getVpnConfig() - .getCountryVanillaKickReason() - .replace("%player%", event.getPlayer().getUsername()) - .replace("%country%", result.response().getCountryName()) - .replace("%code%", result.response().getCountryCode())))); - case DENIED_PROXY -> { - VelocityPlugin.INSTANCE.getLogger().info(event.getPlayer().getUsername() - + " joined on a VPN/Proxy (" + result.response().getMethod() + ")"); - event.setResult(ResultedEvent.ComponentResult.denied(LegacyComponentSerializer.builder() - .character('&') - .build().deserialize(AntiVPN.getInstance().getVpnConfig() - .getKickMessage() - .replace("%player%", event.getPlayer().getUsername()) - .replace("%country%", result.response().getCountryName()) - .replace("%code%", result.response().getCountryCode())))); - } - } + switch (result.resultType()) { + case DENIED_COUNTRY -> + event.setResult( + ResultedEvent.ComponentResult.denied( + LegacyComponentSerializer.builder() + .character('&') + .build() + .deserialize( + AntiVPN.getInstance() + .getVpnConfig() + .getCountryVanillaKickReason() + .replace("%player%", event.getPlayer().getUsername()) + .replace("%country%", result.response().getCountryName()) + .replace("%code%", result.response().getCountryCode())))); + case DENIED_PROXY -> { + VelocityPlugin.INSTANCE + .getLogger() + .info( + event.getPlayer().getUsername() + + " joined on a VPN/Proxy (" + + result.response().getMethod() + + ")"); + event.setResult( + ResultedEvent.ComponentResult.denied( + LegacyComponentSerializer.builder() + .character('&') + .build() + .deserialize( + AntiVPN.getInstance() + .getVpnConfig() + .getKickMessage() + .replace("%player%", event.getPlayer().getUsername()) + .replace("%country%", result.response().getCountryName()) + .replace("%code%", result.response().getCountryCode())))); + } } + } - @Override - public void log(Level level, String log, Object... objects) { - VelocityPlugin.INSTANCE.getLogger().log(level, String.format(log, objects)); - } + @Override + public void log(Level level, String log, Object... objects) { + VelocityPlugin.INSTANCE.getLogger().log(level, String.format(log, objects)); + } - @Override - public void log(String log, Object... objects) { - log(Level.INFO, String.format(log, objects)); - } + @Override + public void log(String log, Object... objects) { + log(Level.INFO, String.format(log, objects)); + } - @Override - public void logException(String message, Throwable ex) { - VelocityPlugin.INSTANCE.getLogger().log(Level.SEVERE, message, ex); - } + @Override + public void logException(String message, Throwable ex) { + VelocityPlugin.INSTANCE.getLogger().log(Level.SEVERE, message, ex); + } - @Override - public void runCommand(String command) { - VelocityPlugin.INSTANCE.getServer().getCommandManager() - .executeAsync(VelocityPlugin.INSTANCE.getServer() - .getConsoleCommandSource(), - StringUtil.translateAlternateColorCodes('&', - command)); - } + @Override + public void runCommand(String command) { + VelocityPlugin.INSTANCE + .getServer() + .getCommandManager() + .executeAsync( + VelocityPlugin.INSTANCE.getServer().getConsoleCommandSource(), + StringUtil.translateAlternateColorCodes('&', command)); + } - @Override - public void disablePlugin() { - VelocityPlugin.INSTANCE.getServer().getEventManager().unregisterListener(VelocityPlugin.INSTANCE.getPluginInstance(), this); - VelocityPlugin.INSTANCE.getServer().getCommandManager().unregister("antivpn"); - } + @Override + public void disablePlugin() { + VelocityPlugin.INSTANCE + .getServer() + .getEventManager() + .unregisterListener(VelocityPlugin.INSTANCE.getPluginInstance(), this); + VelocityPlugin.INSTANCE.getServer().getCommandManager().unregister("antivpn"); + } } diff --git a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlayer.java b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlayer.java index eb613e4..b9c5764 100644 --- a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlayer.java +++ b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlayer.java @@ -22,28 +22,28 @@ import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; public class VelocityPlayer extends APIPlayer { - private final Player player; - public VelocityPlayer(Player player) { - super(player.getUniqueId(), player.getUsername(), player.getRemoteAddress().getAddress()); + private final Player player; - this.player = player; - } + public VelocityPlayer(Player player) { + super(player.getUniqueId(), player.getUsername(), player.getRemoteAddress().getAddress()); + this.player = player; + } - @Override - public void sendMessage(String message) { - player.sendMessage(LegacyComponentSerializer.builder().character('&').build().deserialize(message)); - } - - @Override - public void kickPlayer(String reason) { - player.disconnect(LegacyComponentSerializer.builder().character('&').build().deserialize(reason)); - } - - @Override - public boolean hasPermission(String permission) { - return player.hasPermission(permission); - } + @Override + public void sendMessage(String message) { + player.sendMessage( + LegacyComponentSerializer.builder().character('&').build().deserialize(message)); + } + @Override + public void kickPlayer(String reason) { + player.disconnect( + LegacyComponentSerializer.builder().character('&').build().deserialize(reason)); + } + @Override + public boolean hasPermission(String permission) { + return player.hasPermission(permission); + } } diff --git a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlayerExecutor.java b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlayerExecutor.java index f137ba6..18fbe87 100644 --- a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlayerExecutor.java +++ b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlayerExecutor.java @@ -19,40 +19,40 @@ package dev.brighten.antivpn.velocity; import com.velocitypowered.api.proxy.Player; import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.PlayerExecutor; - import java.util.*; import java.util.stream.Collectors; public class VelocityPlayerExecutor implements PlayerExecutor { - private final Map cachedPlayers = new HashMap<>(); + private final Map cachedPlayers = new HashMap<>(); - @Override - public Optional getPlayer(String name) { - Optional player = VelocityPlugin.INSTANCE.getServer().getPlayer(name); + @Override + public Optional getPlayer(String name) { + Optional player = VelocityPlugin.INSTANCE.getServer().getPlayer(name); - return player.map(value -> cachedPlayers.computeIfAbsent(value.getUniqueId(), - key -> new VelocityPlayer(value))); + return player.map( + value -> + cachedPlayers.computeIfAbsent(value.getUniqueId(), key -> new VelocityPlayer(value))); + } - } + @Override + public Optional getPlayer(UUID uuid) { + Optional player = VelocityPlugin.INSTANCE.getServer().getPlayer(uuid); - @Override - public Optional getPlayer(UUID uuid) { - Optional player = VelocityPlugin.INSTANCE.getServer().getPlayer(uuid); + return player.map( + value -> + cachedPlayers.computeIfAbsent(value.getUniqueId(), key -> new VelocityPlayer(value))); + } - return player.map(value -> cachedPlayers.computeIfAbsent(value.getUniqueId(), - key -> new VelocityPlayer(value))); - } + @Override + public void unloadPlayer(UUID uuid) { + cachedPlayers.remove(uuid); + } - @Override - public void unloadPlayer(UUID uuid) { - cachedPlayers.remove(uuid); - } - - @Override - public List getOnlinePlayers() { - return VelocityPlugin.INSTANCE.getServer().getAllPlayers().stream() - .map(pl -> cachedPlayers.computeIfAbsent(pl.getUniqueId(), key -> new VelocityPlayer(pl))) - .collect(Collectors.toList()); - } + @Override + public List getOnlinePlayers() { + return VelocityPlugin.INSTANCE.getServer().getAllPlayers().stream() + .map(pl -> cachedPlayers.computeIfAbsent(pl.getUniqueId(), key -> new VelocityPlayer(pl))) + .collect(Collectors.toList()); + } } diff --git a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java index 5f76d1d..6c5751a 100644 --- a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java +++ b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/VelocityPlugin.java @@ -25,93 +25,96 @@ import dev.brighten.antivpn.database.mongo.MongoVPN; import dev.brighten.antivpn.database.sql.MySqlVPN; import dev.brighten.antivpn.loader.LoaderBootstrap; import dev.brighten.antivpn.velocity.command.VelocityCommand; +import java.io.File; +import java.util.Map; +import java.util.logging.Logger; +import javax.annotation.Nullable; import lombok.Getter; import org.bstats.charts.SimplePie; import org.bstats.velocity.Metrics; -import javax.annotation.Nullable; -import java.io.File; -import java.util.Map; -import java.util.logging.Logger; - @Getter public class VelocityPlugin implements LoaderBootstrap { - private final ProxyServer server; - private final Logger logger; - private final Metrics.Factory metricsFactory; - private File dataFolder; + private final ProxyServer server; + private final Logger logger; + private final Metrics.Factory metricsFactory; + private File dataFolder; - @Nullable - private Metrics metrics; + @Nullable private Metrics metrics; + public static VelocityPlugin INSTANCE; - public static VelocityPlugin INSTANCE; + private final Object pluginInstance; - private final Object pluginInstance; + public VelocityPlugin(Map, Object> objectsMap) { + this.server = (ProxyServer) objectsMap.get(ProxyServer.class); + this.logger = (Logger) objectsMap.get(Logger.class); + this.metricsFactory = (Metrics.Factory) objectsMap.get(String.class); + this.pluginInstance = objectsMap.get(LoaderBootstrap.class); + } - public VelocityPlugin(Map, Object> objectsMap) { - this.server = (ProxyServer) objectsMap.get(ProxyServer.class); - this.logger = (Logger) objectsMap.get(Logger.class); - this.metricsFactory = (Metrics.Factory) objectsMap.get(String.class); - this.pluginInstance = objectsMap.get(LoaderBootstrap.class); + private String getDatabaseType() { + VPNDatabase database = AntiVPN.getInstance().getDatabase(); + if (database instanceof MySqlVPN) { + return "MySQL"; + } else if (database instanceof H2VPN) { + return "H2"; + } else if (database instanceof MongoVPN) { + return "MongoDB"; + } else { + return "No-Database"; + } + } + + @Override + public void onLoad(File dataFolder) { + this.dataFolder = dataFolder; + } + + @Override + public void onEnable() { + INSTANCE = this; + logger.info("Loading config..."); + + // Loading plugin + logger.info("Starting AntiVPN services..."); + AntiVPN.start(new VelocityListener(), new VelocityPlayerExecutor(), dataFolder); + + if (AntiVPN.getInstance().getVpnConfig().metrics()) { + logger.info("Starting metrics..."); + metrics = metricsFactory.make(pluginInstance, 12791); + + metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType)); } - private String getDatabaseType() { - VPNDatabase database = AntiVPN.getInstance().getDatabase(); - if(database instanceof MySqlVPN) { - return "MySQL"; - } else if(database instanceof H2VPN) { - return "H2"; - } else if(database instanceof MongoVPN) { - return "MongoDB"; - } else { - return "No-Database"; - } + logger.info("Registering commands..."); + for (Command command : AntiVPN.getInstance().getCommands()) { + server + .getCommandManager() + .register( + server + .getCommandManager() + .metaBuilder(command.name()) + .aliases(command.aliases()) + .build(), + new VelocityCommand(command)); + } + } + + @Override + public void onDisable() { + AntiVPN.getInstance().getExecutor().log("Disabling AntiVPN..."); + + if (AntiVPN.getInstance().getDatabase() != null) { + AntiVPN.getInstance().stop(); } - @Override - public void onLoad(File dataFolder) { - this.dataFolder = dataFolder; + if (metrics != null) { + metrics = null; } - @Override - public void onEnable() { - INSTANCE = this; - logger.info("Loading config..."); - - //Loading plugin - logger.info("Starting AntiVPN services..."); - AntiVPN.start(new VelocityListener(), new VelocityPlayerExecutor(), dataFolder); - - - if(AntiVPN.getInstance().getVpnConfig().metrics()) { - logger.info("Starting metrics..."); - metrics = metricsFactory.make(pluginInstance, 12791); - - metrics.addCustomChart(new SimplePie("database_used", this::getDatabaseType)); - } - - logger.info("Registering commands..."); - for (Command command : AntiVPN.getInstance().getCommands()) { - server.getCommandManager().register(server.getCommandManager().metaBuilder(command.name()) - .aliases(command.aliases()).build(), new VelocityCommand(command)); - } - } - - @Override - public void onDisable() { - AntiVPN.getInstance().getExecutor().log("Disabling AntiVPN..."); - - if (AntiVPN.getInstance().getDatabase() != null) { - AntiVPN.getInstance().stop(); - } - - if (metrics != null) { - metrics = null; - } - - INSTANCE = null; - logger.info("Disabled AntiVPN."); - } + INSTANCE = null; + logger.info("Disabled AntiVPN."); + } } diff --git a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/command/VelocityCommand.java b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/command/VelocityCommand.java index b372f0b..7ebba0e 100644 --- a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/command/VelocityCommand.java +++ b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/command/VelocityCommand.java @@ -20,95 +20,127 @@ import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.command.Command; -import lombok.val; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.TextColor; -import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; - import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.IntStream; +import lombok.val; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; public class VelocityCommand implements SimpleCommand { - private final Command command; + private final Command command; - public VelocityCommand(Command command) { - this.command = command; + public VelocityCommand(Command command) { + this.command = command; + } + + @Override + public void execute(Invocation invocation) { + CommandSource sender = invocation.source(); + if (!invocation.source().hasPermission("antivpn.command.*") + && !invocation.source().hasPermission(command.permission())) { + invocation + .source() + .sendMessage( + LegacyComponentSerializer.builder() + .character('&') + .build() + .deserialize( + AntiVPN.getInstance() + .getMessageHandler() + .getString("no-permission") + .getMessage())); + return; } - @Override - public void execute(Invocation invocation) { - CommandSource sender = invocation.source(); - if(!invocation.source().hasPermission("antivpn.command.*") - && !invocation.source().hasPermission(command.permission())) { - invocation.source().sendMessage(LegacyComponentSerializer.builder().character('&') - .build().deserialize(AntiVPN.getInstance().getMessageHandler() - .getString("no-permission").getMessage())); + val children = command.children(); + + String[] args = invocation.arguments(); + if (children.length > 0 && args.length > 0) { + for (Command child : children) { + if (child.name().equalsIgnoreCase(args[0]) + || Arrays.stream(child.aliases()).anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) { + if (!sender.hasPermission("antivpn.command.*") + && !sender.hasPermission(child.permission())) { + invocation + .source() + .sendMessage( + LegacyComponentSerializer.builder() + .character('&') + .build() + .deserialize( + AntiVPN.getInstance() + .getMessageHandler() + .getString("no-permission") + .getMessage())); + invocation + .source() + .sendMessage( + Component.text("No permission").toBuilder() + .color(TextColor.color(255, 0, 0)) + .build()); return; + } + sender.sendMessage( + LegacyComponentSerializer.builder() + .character('&') + .build() + .deserialize( + child.execute( + new VelocityCommandExecutor(sender), + IntStream.range(0, args.length - 1) + .mapToObj(i -> args[i + 1]) + .toArray(String[]::new)))); + return; } + } + } - val children = command.children(); + sender.sendMessage( + LegacyComponentSerializer.builder() + .character('&') + .build() + .deserialize(command.execute(new VelocityCommandExecutor(sender), args))); + } - String[] args = invocation.arguments(); - if(children.length > 0 && args.length > 0) { - for (Command child : children) { - if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases()) - .anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) { - if(!sender.hasPermission("antivpn.command.*") - && !sender.hasPermission(child.permission())) { - invocation.source().sendMessage(LegacyComponentSerializer.builder().character('&') - .build().deserialize(AntiVPN.getInstance().getMessageHandler() - .getString("no-permission").getMessage())); - invocation.source().sendMessage(Component.text("No permission") - .toBuilder().color(TextColor.color(255,0,0)).build()); - return; - } - sender.sendMessage(LegacyComponentSerializer.builder().character('&').build() - .deserialize(child.execute(new VelocityCommandExecutor(sender), IntStream - .range(0, args.length - 1) - .mapToObj(i -> args[i + 1]).toArray(String[]::new)))); - return; - } - } + @Override + public List suggest(Invocation invocation) { + final CommandSource sender = invocation.source(); + final String[] args = invocation.arguments(); + + val children = command.children(); + + if (children.length > 0 && args.length > 0) { + for (dev.brighten.antivpn.command.Command child : children) { + if (child.name().equalsIgnoreCase(args[0]) + || Arrays.stream(child.aliases()) + .anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) { + return child.tabComplete( + new VelocityCommandExecutor(sender), + "alias", + IntStream.range(0, args.length - 1) + .mapToObj(i -> args[i + 1]) + .toArray(String[]::new)); } - - sender.sendMessage(LegacyComponentSerializer.builder().character('&').build() - .deserialize(command.execute(new VelocityCommandExecutor(sender), args))); + } + } else if (children.length > 0) { // && args.length == 0 is always true here + return Arrays.stream(children).map(Command::name).collect(Collectors.toList()); } - @Override - public List suggest(Invocation invocation) { - final CommandSource sender = invocation.source(); - final String[] args = invocation.arguments(); + return command.tabComplete(new VelocityCommandExecutor(sender), "alias", args); + } - val children = command.children(); + @Override + public CompletableFuture> suggestAsync(Invocation invocation) { + return CompletableFuture.supplyAsync(() -> this.suggest(invocation)); + } - if(children.length > 0 && args.length > 0) { - for (dev.brighten.antivpn.command.Command child : children) { - if(child.name().equalsIgnoreCase(args[0]) || Arrays.stream(child.aliases()) - .anyMatch(alias2 -> alias2.equalsIgnoreCase(args[0]))) { - return child.tabComplete(new VelocityCommandExecutor(sender), "alias", IntStream - .range(0, args.length - 1) - .mapToObj(i -> args[i + 1]).toArray(String[]::new)); - } - } - }else if (children.length > 0){ // && args.length == 0 is always true here - return Arrays.stream(children).map(Command::name).collect(Collectors.toList()); - } - - return command.tabComplete(new VelocityCommandExecutor(sender), "alias", args); - } - - @Override - public CompletableFuture> suggestAsync(Invocation invocation) { - return CompletableFuture.supplyAsync(() -> this.suggest(invocation)); - } - - @Override - public boolean hasPermission(Invocation invocation) { - return SimpleCommand.super.hasPermission(invocation); - } + @Override + public boolean hasPermission(Invocation invocation) { + return SimpleCommand.super.hasPermission(invocation); + } } diff --git a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/command/VelocityCommandExecutor.java b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/command/VelocityCommandExecutor.java index 1a08dbf..b502ed9 100644 --- a/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/command/VelocityCommandExecutor.java +++ b/Velocity/VelocityPlugin/src/main/java/dev/brighten/antivpn/velocity/command/VelocityCommandExecutor.java @@ -21,36 +21,38 @@ import com.velocitypowered.api.proxy.Player; import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.command.CommandExecutor; +import java.util.Optional; import lombok.RequiredArgsConstructor; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; -import java.util.Optional; - @RequiredArgsConstructor public class VelocityCommandExecutor implements CommandExecutor { - private final CommandSource sender; + private final CommandSource sender; - @Override - public void sendMessage(String message, Object... objects) { - sender.sendMessage(LegacyComponentSerializer.builder().character('&').build() - .deserialize(String.format(message, objects))); - } + @Override + public void sendMessage(String message, Object... objects) { + sender.sendMessage( + LegacyComponentSerializer.builder() + .character('&') + .build() + .deserialize(String.format(message, objects))); + } - @Override - public boolean hasPermission(String permission) { - return sender.hasPermission(permission); - } + @Override + public boolean hasPermission(String permission) { + return sender.hasPermission(permission); + } - @Override - public Optional getPlayer() { - if(!isPlayer()) return Optional.empty(); + @Override + public Optional getPlayer() { + if (!isPlayer()) return Optional.empty(); - return AntiVPN.getInstance().getPlayerExecutor().getPlayer(((Player) sender).getUniqueId()); - } + return AntiVPN.getInstance().getPlayerExecutor().getPlayer(((Player) sender).getUniqueId()); + } - @Override - public boolean isPlayer() { - return sender instanceof Player; - } + @Override + public boolean isPlayer() { + return sender instanceof Player; + } } diff --git a/Velocity/VelocityPlugin/src/test/java/dev/brighten/antivpn/velocity/VelocityListenerTest.java b/Velocity/VelocityPlugin/src/test/java/dev/brighten/antivpn/velocity/VelocityListenerTest.java index 9a71225..6a367d0 100644 --- a/Velocity/VelocityPlugin/src/test/java/dev/brighten/antivpn/velocity/VelocityListenerTest.java +++ b/Velocity/VelocityPlugin/src/test/java/dev/brighten/antivpn/velocity/VelocityListenerTest.java @@ -1,126 +1,127 @@ package dev.brighten.antivpn.velocity; +import static org.mockito.Mockito.*; + import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.proxy.Player; import dev.brighten.antivpn.AntiVPN; +import dev.brighten.antivpn.StandardTest; import dev.brighten.antivpn.api.PlayerExecutor; import dev.brighten.antivpn.api.VPNConfig; import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.message.MessageHandler; import dev.brighten.antivpn.message.VpnString; import dev.brighten.antivpn.web.objects.VPNResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; - import java.util.logging.Logger; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -import static org.mockito.Mockito.*; +public class VelocityListenerTest extends StandardTest { -public class VelocityListenerTest { + private VelocityListener listener; + private VPNConfig config; + private PlayerExecutor playerExecutor; + private VPNExecutor vpnExecutor; - private VelocityListener listener; - private AntiVPN antiVPN; - private VPNConfig config; - private PlayerExecutor playerExecutor; - private VPNExecutor vpnExecutor; - private MessageHandler messageHandler; - private VelocityPlugin velocityPlugin; + @BeforeEach + public void setUp() throws Exception { + AntiVPN antiVPN = mock(AntiVPN.class); + config = mock(VPNConfig.class); + playerExecutor = mock(PlayerExecutor.class); + vpnExecutor = mock(VPNExecutor.class); + MessageHandler messageHandler = mock(MessageHandler.class); + VelocityPlugin velocityPlugin = mock(VelocityPlugin.class); - @BeforeEach - public void setUp() throws Exception { - antiVPN = mock(AntiVPN.class); - config = mock(VPNConfig.class); - playerExecutor = mock(PlayerExecutor.class); - vpnExecutor = mock(VPNExecutor.class); - messageHandler = mock(MessageHandler.class); - velocityPlugin = mock(VelocityPlugin.class); + when(antiVPN.getVpnConfig()).thenReturn(config); + when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor); + when(antiVPN.getExecutor()).thenReturn(vpnExecutor); + when(antiVPN.getMessageHandler()).thenReturn(messageHandler); - when(antiVPN.getVpnConfig()).thenReturn(config); - when(antiVPN.getPlayerExecutor()).thenReturn(playerExecutor); - when(antiVPN.getExecutor()).thenReturn(vpnExecutor); - when(antiVPN.getMessageHandler()).thenReturn(messageHandler); + when(velocityPlugin.getLogger()).thenReturn(Logger.getLogger("AntiVPN")); - when(velocityPlugin.getLogger()).thenReturn(Logger.getLogger("AntiVPN")); + when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty()); + when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList()); + when(config.getCountryList()).thenReturn(java.util.Collections.emptyList()); + when(config.isKickPlayers()).thenReturn(true); + when(config.getKickMessage()).thenReturn("Blocked!"); - when(playerExecutor.getPlayer(any(UUID.class))).thenReturn(Optional.empty()); - when(config.getPrefixWhitelists()).thenReturn(java.util.Collections.emptyList()); - when(config.getCountryList()).thenReturn(java.util.Collections.emptyList()); - when(config.isKickPlayers()).thenReturn(true); - when(config.getKickMessage()).thenReturn("Blocked!"); + VpnString mockVpnString = mock(VpnString.class); + when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!"); + when(messageHandler.getString(anyString())).thenReturn(mockVpnString); - VpnString mockVpnString = mock(VpnString.class); - when(mockVpnString.getFormattedMessage(any())).thenReturn("Blocked!"); - when(messageHandler.getString(anyString())).thenReturn(mockVpnString); + when(vpnExecutor.checkIp(anyString())) + .thenReturn( + CompletableFuture.completedFuture( + VPNResponse.builder() + .success(true) + .proxy(false) + .ip("127.0.0.1") + .method("N/A") + .countryName("N/A") + .city("N/A") + .countryCode("N/A") + .build())); - when(vpnExecutor.checkIp(anyString())).thenReturn(CompletableFuture.completedFuture( - VPNResponse.builder().success(true).proxy(false).ip("127.0.0.1") - .method("N/A").countryName("N/A").city("N/A").countryCode("N/A").build() - )); + // Use reflection to set the private static INSTANCE field + Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); + instanceField.setAccessible(true); + instanceField.set(null, antiVPN); - // Use reflection to set the private static INSTANCE field - Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); - instanceField.setAccessible(true); - instanceField.set(null, antiVPN); + Field pluginInstanceField = VelocityPlugin.class.getDeclaredField("INSTANCE"); + pluginInstanceField.setAccessible(true); + pluginInstanceField.set(null, velocityPlugin); - Field pluginInstanceField = VelocityPlugin.class.getDeclaredField("INSTANCE"); - pluginInstanceField.setAccessible(true); - pluginInstanceField.set(null, velocityPlugin); + listener = new VelocityListener(); + } - listener = new VelocityListener(); - } + @AfterEach + public void tearDown() throws Exception { + // Reset the singletons + Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); + instanceField.setAccessible(true); + instanceField.set(null, null); - @AfterEach - public void tearDown() throws Exception { - // Reset the singletons - Field instanceField = AntiVPN.class.getDeclaredField("INSTANCE"); - instanceField.setAccessible(true); - instanceField.set(null, null); + Field pluginInstanceField = VelocityPlugin.class.getDeclaredField("INSTANCE"); + pluginInstanceField.setAccessible(true); + pluginInstanceField.set(null, null); + } - Field pluginInstanceField = VelocityPlugin.class.getDeclaredField("INSTANCE"); - pluginInstanceField.setAccessible(true); - pluginInstanceField.set(null, null); - } + @Test + public void testLoginEventAllowed() throws Exception { + LoginEvent event = mock(LoginEvent.class); + Player player = mock(Player.class); - @Test - public void testLoginEventAllowed() throws Exception { - LoginEvent event = mock(LoginEvent.class); - Player player = mock(Player.class); + when(event.getPlayer()).thenReturn(player); + when(player.getUniqueId()).thenReturn(UUID.randomUUID()); + when(player.getUsername()).thenReturn("TestPlayer"); + when(player.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 12345)); - when(event.getPlayer()).thenReturn(player); - when(player.getUniqueId()).thenReturn(UUID.randomUUID()); - when(player.getUsername()).thenReturn("TestPlayer"); - when(player.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 12345)); + listener.onLogin(event); - listener.onLogin(event); + verify(event, never()).setResult(any()); + } - verify(event, never()).setResult(any()); - } + @Test + public void testLoginEventBlocked() throws Exception { + LoginEvent event = mock(LoginEvent.class); + Player player = mock(Player.class); - @Test - public void testLoginEventBlocked() throws Exception { - LoginEvent event = mock(LoginEvent.class); - Player player = mock(Player.class); + when(event.getPlayer()).thenReturn(player); + when(player.getUniqueId()).thenReturn(UUID.randomUUID()); + when(player.getUsername()).thenReturn("ProxyPlayer"); + when(player.getRemoteAddress()).thenReturn(new InetSocketAddress("1.1.1.1", 12345)); - when(event.getPlayer()).thenReturn(player); - when(player.getUniqueId()).thenReturn(UUID.randomUUID()); - when(player.getUsername()).thenReturn("ProxyPlayer"); - when(player.getRemoteAddress()).thenReturn(new InetSocketAddress("1.1.1.1", 12345)); + // Mock proxy response + mockCache(); - // Mock proxy response - when(vpnExecutor.checkIp("1.1.1.1")).thenReturn(CompletableFuture.completedFuture( - VPNResponse.builder().success(true).proxy(true).ip("1.1.1.1") - .method("N/A").countryName("N/A").city("N/A").countryCode("N/A").build() - )); + listener.onLogin(event); - listener.onLogin(event); - - verify(event).setResult(any()); - } + verify(event).setResult(any()); + } } diff --git a/build.gradle b/build.gradle index 9157264..52c8708 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ plugins { id 'java' id 'com.gradleup.shadow' version '9.4.1' + id 'com.diffplug.spotless' version '8.4.0' apply false id 'xyz.jpenilla.run-velocity' version '3.0.2' apply false } @@ -14,7 +15,7 @@ def aggregateTestProjects = [ allprojects { group = 'dev.brighten.antivpn' - version = '1.10.1.1' + version = '1.10.1.2' repositories { maven { url 'https://repo.papermc.io/repository/maven-public/' } @@ -30,6 +31,7 @@ allprojects { } apply plugin: 'java' + apply plugin: 'com.diffplug.spotless' java { toolchain { @@ -42,6 +44,22 @@ allprojects { options.compilerArgs << '-XDignore.symbol.file' } + spotless { + java { + target 'src/**/*.java' + removeUnusedImports() + googleJavaFormat() + cleanthat() + + trimTrailingWhitespace() + endWithNewline() + } + } + + tasks.named('check') { + dependsOn tasks.named('spotlessCheck') + } + dependencies { compileOnly 'org.projectlombok:lombok:1.18.44' annotationProcessor 'org.projectlombok:lombok:1.18.44' diff --git a/gradle.properties b/gradle.properties index 5f1ed7b..e68633c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,2 @@ -org.gradle.caching=true \ No newline at end of file +org.gradle.caching=true +org.gradle.parallel=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index dbc3ce4..1a70468 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle index 10c35cd..dd72e22 100644 --- a/settings.gradle +++ b/settings.gradle @@ -14,5 +14,3 @@ include 'Sponge:SpongeLoader' include 'Velocity:VelocityPlugin' include 'Velocity:VelocityLoader' - -