diff --git a/eclipse-language-servers/org.springframework.tooling.ls.integration.repository/category.xml b/eclipse-language-servers/org.springframework.tooling.ls.integration.repository/category.xml index 72dccc22e..301b36d56 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.integration.repository/category.xml +++ b/eclipse-language-servers/org.springframework.tooling.ls.integration.repository/category.xml @@ -40,14 +40,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -65,7 +93,7 @@ - + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.classpath b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.classpath new file mode 100644 index 000000000..eca7bdba8 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.project b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.project new file mode 100644 index 000000000..052901b0a --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.project @@ -0,0 +1,28 @@ + + + org.springsource.ide.eclipse.commons.boot.ls + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.settings/org.eclipse.jdt.core.prefs b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..3e5a4cef4 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,314 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.8 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=1 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false +org.eclipse.jdt.core.formatter.comment.indent_root_tags=false +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=do not insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.lineSplit=120 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=tab +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.settings/org.eclipse.jdt.ui.prefs b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 000000000..fe75b53ff --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,56 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_SpringSource Tool Suite +formatter_settings_version=11 +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_to_enhanced_for_loop=true +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=false +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=true +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=true +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_blocks=true +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.settings/org.eclipse.mylyn.tasks.ui.prefs b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.settings/org.eclipse.mylyn.tasks.ui.prefs new file mode 100644 index 000000000..488179572 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.settings/org.eclipse.mylyn.tasks.ui.prefs @@ -0,0 +1,4 @@ +#Fri Oct 19 17:41:16 PDT 2007 +eclipse.preferences.version=1 +project.repository.kind=jira +project.repository.url=https\://jira.springsource.com diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.settings/org.eclipse.mylyn.team.ui.prefs b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.settings/org.eclipse.mylyn.team.ui.prefs new file mode 100644 index 000000000..7632f5e7d --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/.settings/org.eclipse.mylyn.team.ui.prefs @@ -0,0 +1,3 @@ +#Fri Oct 19 17:41:16 PDT 2007 +commit.comment.template=${task.key}\: ${task.description} +eclipse.preferences.version=1 diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/META-INF/MANIFEST.MF b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/META-INF/MANIFEST.MF new file mode 100644 index 000000000..ac88575ed --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/META-INF/MANIFEST.MF @@ -0,0 +1,13 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Spring Tool Suite Common UI Components +Bundle-SymbolicName: org.springsource.ide.eclipse.commons.boot.ls;singleton:=true +Bundle-Version: 4.8.1.qualifier +Bundle-Vendor: Pivotal Software, Inc. +Require-Bundle: org.eclipse.core.resources, + org.springsource.ide.eclipse.commons.livexp +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Bundle-ActivationPolicy: lazy +Automatic-Module-Name: org.springsource.ide.eclipse.commons.boot.ls +Export-Package: org.springsource.ide.eclipse.commons.boot.ls.remoteapps +Import-Package: org.eclipse.core.runtime diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/about.html b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/about.html new file mode 100644 index 000000000..ae88621cc --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/about.html @@ -0,0 +1,22 @@ + + + + +About Eclipse Integration Commons + + + +

About Eclipse Integration Commons

+

June 30, 2015

+

Abstract

+

The Eclipse Integration Commons project provides components that help +other projects to implement Eclipse-based tooling.

+

License

+

Pivotal Software, Inc. (“Pivotal”) makes available all content +in this download ("Content"). Unless otherwise indicated below, the +Content is provided to you under the terms and conditions of the Eclipse +Public License Version 1.0 ("EPL"). A copy of the EPL is available in +the file called license.txt. For purposes of the EPL, "Program" will +mean the Content.

+ + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/build.properties b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/build.properties new file mode 100644 index 000000000..20aab766d --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/build.properties @@ -0,0 +1,8 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + about.html,\ + plugin.xml +src.includes = about.html + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/plugin.xml b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/plugin.xml new file mode 100644 index 000000000..c90b9f208 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/plugin.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/pom.xml b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/pom.xml new file mode 100644 index 000000000..d747b9ba1 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + org.springframework.boot.ide + org.springframework.boot.ide.servers + 4.8.1-SNAPSHOT + ../pom.xml + + + org.springsource.ide.eclipse.commons.boot.ls + eclipse-plugin + org.springsource.ide.eclipse.commons.boot.ls + + + + + org.eclipse.tycho + tycho-p2-plugin + ${tycho-version} + + + second-generate-p2-metadata + + p2-metadata + + verify + + + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/schema/org.springsource.ide.eclipse.commons.boot.ls.remoteapps.RemoteBootAppsDataHolder.Contributor.exsd b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/schema/org.springsource.ide.eclipse.commons.boot.ls.remoteapps.RemoteBootAppsDataHolder.Contributor.exsd new file mode 100644 index 000000000..1c6ff228c --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/schema/org.springsource.ide.eclipse.commons.boot.ls.remoteapps.RemoteBootAppsDataHolder.Contributor.exsd @@ -0,0 +1,102 @@ + + + + + + + + + [Enter description of this extension point.] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [Enter the first release in which this extension point appears.] + + + + + + + + + [Enter extension point usage example here.] + + + + + + + + + [Enter API information here.] + + + + + + + + + [Enter information about supplied implementation of this extension point.] + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/src/org/springsource/ide/eclipse/commons/boot/ls/remoteapps/ExentionPointLoader.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/src/org/springsource/ide/eclipse/commons/boot/ls/remoteapps/ExentionPointLoader.java new file mode 100644 index 000000000..246d581c9 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/src/org/springsource/ide/eclipse/commons/boot/ls/remoteapps/ExentionPointLoader.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2020 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.boot.ls.remoteapps; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.Platform; +import org.springsource.ide.eclipse.commons.livexp.util.Log; + +public class ExentionPointLoader { + + + @SuppressWarnings("unchecked") + public static List load(Class contributionType) { + String extensionPointId = contributionType.getCanonicalName(); + List constributions = new ArrayList<>(); + for (IConfigurationElement ce : Platform.getExtensionRegistry().getConfigurationElementsFor(extensionPointId)) { + try { + Object contribution = ce.createExecutableExtension("class"); + if (!contributionType.isInstance(contribution)) { + throw new IllegalStateException("Invalid contribution to extension point "+extensionPointId); + } else { + constributions.add((T) contribution); + } + } catch (Exception e) { + Log.log(e); + } + } + return constributions; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/src/org/springsource/ide/eclipse/commons/boot/ls/remoteapps/RemoteBootAppsDataHolder.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/src/org/springsource/ide/eclipse/commons/boot/ls/remoteapps/RemoteBootAppsDataHolder.java new file mode 100644 index 000000000..66a0ed6a3 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.boot.ls/src/org/springsource/ide/eclipse/commons/boot/ls/remoteapps/RemoteBootAppsDataHolder.java @@ -0,0 +1,260 @@ +/******************************************************************************* + * Copyright (c) 2017, 2020 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.boot.ls.remoteapps; + +import java.util.List; +import java.util.function.Supplier; + +import org.springsource.ide.eclipse.commons.livexp.core.LiveSets; +import org.springsource.ide.eclipse.commons.livexp.core.ObservableSet; + +/** + * A RemoteAppsDataHolder provides the means to connect providers of RemoteAppData + * with consumers of RemoteAppData. At present there is one consumer and three producers. + *

+ * The single consumer is the Spring Boot Language Server eclipse plugin. It uses remote app data + * to scrape live (hover, bean etc.) information from remote boot processes. + *

+ * The three producers of the information are: + *

+ * a) the "Remote Boot Apps" preferences page (which is defined in the language server + * plugin and allows for users to manually enter information. + *

+ * b) the BootDashBoard cloudfoundry plugin, which contributes information automatically + * when boot apps are deployed to cf from boot dash with JMX tunnelling enabled. + * c) BootDashBoard itself via GenericRemoteAppElement framework. This provides remote app + * data automatically, for elements that support JMX connections. + *

+ * Note: it would be more logical if this class was defined inside boot.ls plugin, (since it + * is the (only) consumer of the data). But because of how + * STS 4 and STS 3 components are currently being built, it is not possible for STS 3 components + * to depend directly on STS 4 components. It is however possible for STS 4 to depend on + * STS 3 components. I.e. boot.ls can depend on boot.dashboard but not vice-versa. + * This is why {@link RemoteBootAppsDataHolder} + * 'extension point' is defined here, allowing boot.dash and spring.boot.ls to both access + * the api and contribute and/or consume remote-app data. + * + * @author Kris De Volder + */ +public class RemoteBootAppsDataHolder { + + private static RemoteBootAppsDataHolder DEFAULT; + + public synchronized static RemoteBootAppsDataHolder getDefault() { + if (DEFAULT==null) { + DEFAULT = new RemoteBootAppsDataHolder(() -> ExentionPointLoader.load(RemoteBootAppsDataHolder.Contributor.class)); + } + return DEFAULT; + } + + /** + * Json serialization-friendly data class + */ + public static class RemoteAppData { + private String jmxurl; + private String host; + private String urlScheme = "https"; + private String port = "443"; + private boolean keepChecking = true; + //keepChecking defaults to true. Boot dash automatic remote apps should override this explicitly. + //Reason. All other 'sources' of remote apps are 'manual' and we want them to default to + //'keepChecking' even if the user doesn't set this to true manually. + private String processId = null; + private String processName = null; + + public RemoteAppData() { + } + + public RemoteAppData(String jmxurl, String host) { + super(); + this.jmxurl = jmxurl; + this.host = host; + } + + public String getJmxurl() { + return jmxurl; + } + + public void setJmxurl(String jmxurl) { + this.jmxurl = jmxurl; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public String getUrlScheme() { + return urlScheme; + } + + public void setUrlScheme(String urlScheme) { + this.urlScheme = urlScheme; + } + + public String getPort() { + return port; + } + + public void setPort(String port) { + this.port = port; + } + + public boolean isKeepChecking() { + return keepChecking; + } + + public void setKeepChecking(boolean keepChecking) { + this.keepChecking = keepChecking; + } + + public String getProcessId() { + return processId; + } + + public void setProcessId(String processId) { + this.processId = processId; + } + + public String getProcessName() { + return processName; + } + + public void setProcessName(String processName) { + this.processName = processName; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((host == null) ? 0 : host.hashCode()); + result = prime * result + ((jmxurl == null) ? 0 : jmxurl.hashCode()); + result = prime * result + (keepChecking ? 1231 : 1237); + result = prime * result + ((port == null) ? 0 : port.hashCode()); + result = prime * result + ((processId == null) ? 0 : processId.hashCode()); + result = prime * result + ((urlScheme == null) ? 0 : urlScheme.hashCode()); + result = prime * result + ((processId == null) ? 0 : processId.hashCode()); + result = prime * result + ((processName == null) ? 0 : processName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + RemoteAppData other = (RemoteAppData) obj; + if (host == null) { + if (other.host != null) { + return false; + } + } else if (!host.equals(other.host)) { + return false; + } + if (jmxurl == null) { + if (other.jmxurl != null) { + return false; + } + } else if (!jmxurl.equals(other.jmxurl)) { + return false; + } + if (keepChecking != other.keepChecking) { + return false; + } + if (port == null) { + if (other.port != null) { + return false; + } + } else if (!port.equals(other.port)) { + return false; + } + if (processId == null) { + if (other.processId != null) { + return false; + } + } else if (!processId.equals(other.processId)) { + return false; + } + if (processName == null) { + if (other.processName != null) { + return false; + } + } else if (!processName.equals(other.processName)) { + return false; + } + if (urlScheme == null) { + if (other.urlScheme != null) { + return false; + } + } else if (!urlScheme.equals(other.urlScheme)) { + return false; + } + return true; + } + + @Override + public String toString() { + return "RemoteAppData [jmxurl=" + jmxurl + ", host=" + host + ", urlScheme=" + urlScheme + ", port=" + port + + ", keepChecking=" + keepChecking + ", processId=" + processId + ", processName=" + processName + "]"; + } + + } + + /** + * A producer of remote RemoteAppData can contribute its data by injecting a bean + * that implements this interface (via {@link SimpleDIContext}. + */ + public interface Contributor { + ObservableSet getRemoteApps(); + } + + /** + * Computes the union of all Contributors. + */ + private final ObservableSet union; + + public RemoteBootAppsDataHolder(Supplier> contributions) { + union = union(contributions.get()); + } + + public static ObservableSet union(List contributors) { + if (contributors.isEmpty()) { + return LiveSets.emptySet(RemoteAppData.class); + } else if (contributors.size()==1) { + return contributors.get(0).getRemoteApps(); + } else { + ObservableSet union = contributors.get(0).getRemoteApps(); + for (int i = 1; i < contributors.size(); i++) { + union = LiveSets.union(union, contributors.get(i).getRemoteApps()); + } + return union; + } + } + + /** + * A consumer of RemoteAppData uses this method to obtain data + * and/or listen for changes to the data. + */ + public ObservableSet getRemoteApps() { + return union; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.classpath b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.classpath new file mode 100644 index 000000000..eca7bdba8 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.project b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.project new file mode 100644 index 000000000..bc025c92c --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.project @@ -0,0 +1,39 @@ + + + org.springsource.ide.eclipse.commons.core + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + + + 1373409305025 + + 14 + + org.eclipse.ui.ide.multiFilter + 1.0-projectRelativePath-matches-false-false-target + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.settings/org.eclipse.jdt.core.prefs b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..0e156a7fa --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,357 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnull.secondary= +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullable.secondary= +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning +org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning +org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled +org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.8 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=1 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false +org.eclipse.jdt.core.formatter.comment.indent_root_tags=false +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=do not insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert +org.eclipse.jdt.core.formatter.comment.line_length=80 +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.lineSplit=120 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=tab +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.settings/org.eclipse.jdt.ui.prefs b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 000000000..9d420de10 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,56 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_SpringSource Tool Suite +formatter_settings_version=11 +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=true +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_missing_override_annotations_interface_methods=false +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_to_enhanced_for_loop=true +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=false +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=true +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=true +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=false +sp_cleanup.remove_unnecessary_nls_tags=true +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_blocks=true +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.settings/org.eclipse.mylyn.tasks.ui.prefs b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.settings/org.eclipse.mylyn.tasks.ui.prefs new file mode 100644 index 000000000..488179572 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.settings/org.eclipse.mylyn.tasks.ui.prefs @@ -0,0 +1,4 @@ +#Fri Oct 19 17:41:16 PDT 2007 +eclipse.preferences.version=1 +project.repository.kind=jira +project.repository.url=https\://jira.springsource.com diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.settings/org.eclipse.mylyn.team.ui.prefs b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.settings/org.eclipse.mylyn.team.ui.prefs new file mode 100644 index 000000000..7632f5e7d --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/.settings/org.eclipse.mylyn.team.ui.prefs @@ -0,0 +1,3 @@ +#Fri Oct 19 17:41:16 PDT 2007 +commit.comment.template=${task.key}\: ${task.description} +eclipse.preferences.version=1 diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/META-INF/MANIFEST.MF b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/META-INF/MANIFEST.MF new file mode 100644 index 000000000..40208bf5b --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/META-INF/MANIFEST.MF @@ -0,0 +1,40 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: SpringSource Tool Suite (Core) +Bundle-SymbolicName: org.springsource.ide.eclipse.commons.core;singleton:=true +Bundle-Version: 4.8.1.qualifier +Bundle-Activator: org.springsource.ide.eclipse.commons.internal.core.CorePlugin +Bundle-Vendor: Pivotal Software, Inc. +Require-Bundle: org.eclipse.core.resources, + org.eclipse.mylyn.commons.net;bundle-version="[3.3.0,4.0.0)", + org.eclipse.equinox.p2.core;resolution:=optional, + org.eclipse.equinox.p2.repository;resolution:=optional, + org.eclipse.jdt.core, + org.eclipse.jdt.launching, + org.eclipse.wst.common.project.facet.core, + org.apache.commons.collections, + org.apache.log4j, + org.eclipse.platform, + org.springsource.ide.eclipse.commons.frameworks.core;bundle-version="3.5.0", + org.springsource.ide.eclipse.commons.jdk_tools;resolution:=optional, + org.eclipse.text, + org.springsource.ide.eclipse.commons.livexp, + org.eclipse.debug.core, + org.eclipse.jface +Bundle-ActivationPolicy: lazy +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Export-Package: org.springsource.ide.eclipse.commons.core, + org.springsource.ide.eclipse.commons.core.preferences, + org.springsource.ide.eclipse.commons.core.process, + org.springsource.ide.eclipse.commons.core.pstore, + org.springsource.ide.eclipse.commons.core.templates, + org.springsource.ide.eclipse.commons.core.util, + org.springsource.ide.eclipse.commons.internal.core, + org.springsource.ide.eclipse.commons.internal.core.commandhistory, + org.springsource.ide.eclipse.commons.internal.core.net +Import-Package: org.eclipse.core.runtime, + org.eclipse.core.runtime.jobs, + org.eclipse.core.runtime.preferences, + org.eclipse.osgi.util, + org.osgi.framework, + org.osgi.service.prefs diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/about.html b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/about.html new file mode 100644 index 000000000..ae88621cc --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/about.html @@ -0,0 +1,22 @@ + + + + +About Eclipse Integration Commons + + + +

About Eclipse Integration Commons

+

June 30, 2015

+

Abstract

+

The Eclipse Integration Commons project provides components that help +other projects to implement Eclipse-based tooling.

+

License

+

Pivotal Software, Inc. (“Pivotal”) makes available all content +in this download ("Content"). Unless otherwise indicated below, the +Content is provided to you under the terms and conditions of the Eclipse +Public License Version 1.0 ("EPL"). A copy of the EPL is available in +the file called license.txt. For purposes of the EPL, "Program" will +mean the Content.

+ + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/build.properties b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/build.properties new file mode 100644 index 000000000..101ae799c --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/build.properties @@ -0,0 +1,7 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + about.html,\ + plugin.xml +src.includes = about.html diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/plugin.xml b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/plugin.xml new file mode 100644 index 000000000..e808ee60a --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/plugin.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/pom.xml b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/pom.xml new file mode 100644 index 000000000..9da2ab8dc --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + + org.springframework.boot.ide + org.springframework.boot.ide.servers + 4.8.1-SNAPSHOT + ../pom.xml + + + org.springsource.ide.eclipse.commons.core + eclipse-plugin + org.springsource.ide.eclipse.commons.core + + + + + org.eclipse.tycho + tycho-p2-plugin + ${tycho-version} + + + second-generate-p2-metadata + + p2-metadata + + verify + + + + + org.eclipse.tycho + tycho-compiler-plugin + + + + jdk + tools.jar + ${java.version} + system + ${java.home}/../lib/tools.jar + + + + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/schema/resources.exsd b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/schema/resources.exsd new file mode 100644 index 000000000..c717ac343 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/schema/resources.exsd @@ -0,0 +1,160 @@ + + + + + + + + + [Enter description of this extension point.] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [Enter the first release in which this extension point appears.] + + + + + + + + + [Enter extension point usage example here.] + + + + + + + + + [Enter API information here.] + + + + + + + + + [Enter information about supplied implementation of this extension point.] + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/CommandHistoryProvider.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/CommandHistoryProvider.java new file mode 100644 index 000000000..bf68b133b --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/CommandHistoryProvider.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; +import org.springsource.ide.eclipse.commons.internal.core.commandhistory.CommandHistory; + + +/** + * Provides access to {@link ICommandHistory} implementations. + * @author Christian Dupuis + * @author Kris De Volder + * @since 2.5.0 + */ +public class CommandHistoryProvider { + + private static final Map COMMAND_HISTORIES = new HashMap(); + + public static synchronized ICommandHistory getCommandHistory(String historyId, String natureId) { + String key = getKey(historyId, natureId); + if (COMMAND_HISTORIES.containsKey(key)) { + return COMMAND_HISTORIES.get(key); + } + else { + try { + CommandHistory commandHistory = new CommandHistory(historyId, natureId, true); + COMMAND_HISTORIES.put(key, commandHistory); + return commandHistory; + } + catch (CoreException e) { + CorePlugin.log( + "Warning: couldn't get persistent command history, using non-persistent history instead", e); + return new CommandHistory(historyId, natureId); + } + } + } + + private static String getKey(String historyId, String natureId) { + Assert.isNotNull(historyId); + Assert.isNotNull(natureId); + return new StringBuilder().append(historyId).append("_").append(natureId).toString(); + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/CoreUtil.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/CoreUtil.java new file mode 100644 index 000000000..177798edd --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/CoreUtil.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import java.util.Properties; + +import org.eclipse.core.runtime.Assert; + +/** + * @author Steffen Pingel + */ +public class CoreUtil { + + /** + * Replaces placeholders in text with values from + * properties. Placeholders use the following format: + * ${key}. If the key is not found in properties + * the placeholder is retained. + * + * @param text the text + * @param properties key value pairs for substitution + * @return the substituted text + */ + public static String substitute(String text, Properties properties) { + Assert.isNotNull(text); + Assert.isNotNull(properties); + String[] segments = text.split("\\$\\{"); + StringBuffer sb = new StringBuffer(text.length()); + sb.append(segments[0]); + for (int i = 1; i < segments.length; i++) { + String segment = segments[i]; + String substitution = null; + int brace = segment.indexOf('}'); + if (brace > 0) { + String keyword = segment.substring(0, brace); + substitution = properties.getProperty(keyword); + } + + if (substitution != null) { + sb.append(substitution); + sb.append(segment.substring(brace + 1)); + } + else { + sb.append("${"); + sb.append(segment); + } + } + return sb.toString(); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/Entry.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/Entry.java new file mode 100644 index 000000000..1f4d03ce3 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/Entry.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import java.io.Serializable; + +/** + * @author Andrew Eisenberg + * @author Christian Dupuis + * @author Kris De Volder + * @since 2.5.0 + */ +public class Entry implements Serializable { + + private static final long serialVersionUID = 1L; + + private final String command; + + private final String project; + + public Entry(String command, String project) { + super(); + this.command = command; + this.project = project; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Entry other = (Entry) obj; + if (command == null) { + if (other.command != null) { + return false; + } + } + else if (!command.equals(other.command)) { + return false; + } + if (project == null) { + if (other.project != null) { + return false; + } + } + else if (!project.equals(other.project)) { + return false; + } + return true; + } + + public String getCommand() { + return command; + } + + public String getMenuLabel() { + return getCommand().trim() + " (" + getProject().trim() + ")"; + } + + public String getProject() { + return project; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((command == null) ? 0 : command.hashCode()); + result = prime * result + ((project == null) ? 0 : project.hashCode()); + return result; + } + + @Override + public String toString() { + return "Entry(" + command + ", " + project + ")"; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/FileUtil.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/FileUtil.java new file mode 100644 index 000000000..c6c9837fd --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/FileUtil.java @@ -0,0 +1,192 @@ +/******************************************************************************* + * Copyright (c) 2012, 2018 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.channels.FileChannel; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Plugin; +import org.eclipse.core.runtime.Status; +import org.osgi.framework.Bundle; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; + + +/** + * @author Steffen Pingel + * @author Leo Dos Santos + * @author Christian Dupuis + * @author Kris De Volder + */ +public class FileUtil { + + private static final int MAX_FILE_SIZE = 1024 * 1024; + + public static final int BUFFER_SIZE = 4096; + + public static void copyDirectory(File sourceDirectory, File targetDirectory, IProgressMonitor monitor) + throws CoreException { + File[] files = sourceDirectory.listFiles(); + if (files != null) { + for (File sourceFile : files) { + File targetFile = new File(targetDirectory, sourceFile.getName()); + if (sourceFile.isDirectory()) { + targetFile.mkdir(); + copyDirectory(sourceFile, targetFile, monitor); + } + else { + copyFile(sourceFile, targetFile, monitor); + } + } + } + } + + public static void copyFile(File sourceFile, File targetFile, IProgressMonitor monitor) throws CoreException { + try { + monitor.beginTask("Copying file", (int) sourceFile.length()); + + FileInputStream in = new FileInputStream(sourceFile); + try { + writeToFile(in, targetFile, monitor); + } + finally { + in.close(); + } + } + catch (IOException e) { + throw new CoreException(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "Could not copy file from " + + sourceFile + " to " + targetFile, e)); + } + finally { + monitor.done(); + } + } + + public static void writeToFile(InputStream in, File targetFile, IProgressMonitor monitor) + throws FileNotFoundException, IOException { + FileOutputStream out = new FileOutputStream(targetFile); + try { + int len; + byte[] buffer = new byte[BUFFER_SIZE]; + while ((len = in.read(buffer)) > 0) { + Policy.checkCancelled(monitor); + + out.write(buffer, 0, len); + monitor.worked(len); + } + } + finally { + out.close(); + } + } + + public static File getFile(Bundle bundle, String filename) throws CoreException { + URL url = bundle.getResource(filename); + if (url == null) { + throw new CoreException(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "Could not locate file: " + + filename, new FileNotFoundException())); + } + + try { + return new File(FileLocator.toFileURL(url).getPath()); + } + catch (Exception e) { + throw new CoreException(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "Could not locate file: " + + filename, e)); + } + } + + public static File getFile(Plugin plugin, String filename) throws CoreException { + return getFile(plugin.getBundle(), filename); + } + + public static String readFile(File sourceFile, IProgressMonitor monitor) throws CoreException { + long length = sourceFile.length(); + if (length > MAX_FILE_SIZE) { + throw new CoreException(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "File \"" + sourceFile + + "\" is too large")); + } + + try { + monitor.beginTask("Reading file", (int) length); + + FileReader in = new FileReader(sourceFile); + try { + StringBuilder sb = new StringBuilder((int) length); + int len; + char[] buffer = new char[BUFFER_SIZE]; + while ((len = in.read(buffer)) > 0) { + Policy.checkCancelled(monitor); + + sb.append(buffer, 0, len); + monitor.worked(len); + } + return sb.toString(); + } + finally { + in.close(); + } + } + catch (IOException e) { + throw new CoreException(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "Could not read file \"" + + sourceFile + "\"", e)); + } + finally { + monitor.done(); + } + } + + private static String[] BINARY_EXTENSIONS = new String[] { "jar", "gif", "jpg", "jpeg", ".class", "png" }; + + public static void copy(File source, File target) throws IOException { + FileInputStream sourceOutStream = new FileInputStream(source); + FileOutputStream targetOutStream = new FileOutputStream(target); + FileChannel sourceChannel = sourceOutStream.getChannel(); + FileChannel targetChannel = targetOutStream.getChannel(); + sourceChannel.transferTo(0, sourceChannel.size(), targetChannel); + sourceChannel.close(); + targetChannel.close(); + sourceOutStream.close(); + targetOutStream.close(); + } + + public static boolean isBinaryFile(File file) { + String extension = FileUtil.getExtension(file); + if (extension != null) { + for (String binaryExtension : BINARY_EXTENSIONS) { + if (binaryExtension.equals(extension)) { + return true; + } + } + } + return false; + } + + public static String getExtension(File file) { + String fileName = file.getName(); + int extensionIndex = fileName.lastIndexOf('.'); + if (extensionIndex == -1) { + return null; + } + return fileName.substring(extensionIndex + 1); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/HttpUtil.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/HttpUtil.java new file mode 100644 index 000000000..6bc9e3da0 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/HttpUtil.java @@ -0,0 +1,175 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLConnection; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.osgi.util.NLS; +import org.springsource.ide.eclipse.commons.core.util.IOUtil; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; +import org.springsource.ide.eclipse.commons.internal.core.net.HttpClientTransportService; +import org.springsource.ide.eclipse.commons.internal.core.net.ITransportService; +import org.springsource.ide.eclipse.commons.internal.core.net.P2TransportService; + +/** + * Provides helper methods for downloading files. + * @author Steffen Pingel + */ +public class HttpUtil { + + private static ITransportService transport; + + public static IStatus download(String url, File archiveFile, File targetDirectory, IProgressMonitor monitor) { + return download(url, archiveFile, targetDirectory, null, monitor); + } + + public static IStatus download(String url, File archiveFile, File targetDirectory, String prefix, + IProgressMonitor monitor) { + + if (monitor.isCanceled()) { + return Status.CANCEL_STATUS; + } + + SubMonitor progress = SubMonitor.convert(monitor, 100); + + targetDirectory.mkdirs(); + + // download archive file + try { + try { + OutputStream out = new BufferedOutputStream(new FileOutputStream(archiveFile)); + try { + HttpUtil.download(new URI(url), out, progress.newChild(70)); + } + catch (CoreException e) { + return new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, NLS.bind( + "I/O error while retrieving data: {0}", e.getMessage()), e); + } + catch (URISyntaxException e) { + return new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, NLS.bind("Invalid URL: {0}", url), e); + } + finally { + out.close(); + } + } + catch (IOException e) { + return new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "I/O error while retrieving data", e); + } + + // extract archive file + try { + URL fileUrl = archiveFile.toURI().toURL(); + ZipFileUtil.unzip(fileUrl, targetDirectory, prefix, progress.newChild(30)); + if (targetDirectory.listFiles().length <= 0) { + String message = NLS.bind("Zip file {0} appears to be empty", archiveFile); + return new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, message); + } + } + catch (IOException e) { + return new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "Error while extracting archive", e); + } + } + finally { + archiveFile.delete(); + } + return Status.OK_STATUS; + } + + public static void download(URI uri, OutputStream out, IProgressMonitor monitor) throws CoreException { + String protocol = uri.getScheme(); + if ("file".equals(protocol)) { + // Yes. it is a bit strange that HttpUtil knows how to read from + // file url. But it is just easier that + // way. Don't need to special case file urls in other places. + // We should consider renaming this class but it has the potential + // of breaking a lot of dependencies. + File f = new File(uri); + FileInputStream contents = null; + try { + contents = new FileInputStream(f); + byte[] buf = new byte[40 * 1024]; + int read; + while ((read = contents.read(buf)) >= 0) { + // read = -1 means EOF + // read == 0 probably is impossible but handle it anyway. + if (read > 0) { + out.write(buf, 0, read); + } + } + } + catch (IOException e) { + throw new CoreException(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, e.getMessage(), e)); + } + finally { + try { + if (contents != null) { + contents.close(); + } + } + catch (IOException e) { + } + } + } + else { + getTransport().download(uri, out, monitor); + } + } + + public static long getLastModified(URI location, IProgressMonitor monitor) throws CoreException { + return getTransport().getLastModified(location, monitor); + } + + public static synchronized ITransportService getTransport() { + if (transport == null) { + if (Platform.isRunning()) { + try { + transport = new P2TransportService(); + } + catch (ClassNotFoundException e) { + // fall back to HttpClientTransport + } + } + if (transport == null) { + transport = new HttpClientTransportService(); + } + } + return transport; + } + + public static InputStream stream(URI uri, IProgressMonitor monitor) throws CoreException { + return getTransport().stream(uri, monitor); + } + + public static void ping(URI uri) throws MalformedURLException, IOException, CoreException { + URLConnection connection = uri.toURL().openConnection(); + connection.setConnectTimeout(500); + InputStream input = connection.getInputStream(); + IOUtil.consume(input); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/ICommandHistory.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/ICommandHistory.java new file mode 100644 index 000000000..9a0b87716 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/ICommandHistory.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import java.util.List; + +/** + * @author Christian Dupuis + * @author Kris De Volder + * @since 2.5.0 + */ +public interface ICommandHistory { + + /** + * The default value for maxSize. + */ + int DEFAULT_MAX_SIZE = 100; + + void add(Entry entry); + + void clear(); + + Entry getLast(); + + List getRecentValid(int limit); + + boolean isEmpty(); + + int size(); + + Iterable validEntries(); + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/JdtUtils.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/JdtUtils.java new file mode 100644 index 000000000..b9f4a3976 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/JdtUtils.java @@ -0,0 +1,944 @@ +// COPIED from spring-ide org.springframework.ide.eclipse.core.java.JdtUtils +/******************************************************************************* + * Copyright (c) 2012 - 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IField; +import org.eclipse.jdt.core.IImportDeclaration; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaModel; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.ITypeParameter; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.Signature; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.launching.JavaRuntime; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; + + +/** + * Utility class that provides several helper methods for working with Eclipse's + * JDT. + * @author Christian Dupuis + * @author Martin Lippert + * @author Leo Dos Santos + * @since 2.0 + */ +public class JdtUtils { + + public static final String CLASS_FILE_EXTENSION = ".class"; + + public static final String GROOVY_FILE_EXTENSION = ".groovy"; + + public static final String JAVA_FILE_EXTENSION = ".java"; + + private static final String AJDT_CLASS = "org.eclipse.ajdt.core.javaelements.AJCompilationUnitManager"; + + private static final String AJDT_NATURE = "org.eclipse.ajdt.ui.ajnature"; + + private static final String CLASSPATH_FILENAME = ".classpath"; + + private static final boolean IS_AJDT_PRESENT = isAjdtPresent(); + + /** Suffix for array class names: "[]" */ + private static final String ARRAY_SUFFIX = "[]"; + + /** + * Creates specified Java project. + */ + public static IJavaProject createJavaProject(String projectName, IProgressMonitor monitor) throws CoreException { + IProject project = SpringCoreUtils.createProject(projectName, null, monitor); + if (monitor.isCanceled()) { + throw new OperationCanceledException(); + } + if (!project.hasNature(JavaCore.NATURE_ID)) { + SpringCoreUtils.addProjectNature(project, JavaCore.NATURE_ID, monitor); + } + IJavaProject jproject = JavaCore.create(project); + // append JRE entry + jproject.setRawClasspath(new IClasspathEntry[] { getJreVariableEntry() }, monitor); + jproject.setOutputLocation(project.getFullPath(), monitor); + if (monitor.isCanceled()) { + throw new OperationCanceledException(); + } + return jproject; + } + + /** + * Checks if the given type implements/extends + * className. + */ + // public static boolean doesImplement(IResource resource, IType type, + // String className) { + // if (resource == null || type == null || className == null) { + // return false; + // } + // if (className.startsWith("java.") || className.startsWith("javax.")) { + // try { + // ClassLoader cls = getClassLoader(resource.getProject(), null); + // Class typeClass = cls.loadClass(type.getFullyQualifiedName('$')); + // Class interfaceClass = cls.loadClass(className); + // return typeClass.equals(interfaceClass) || + // interfaceClass.isAssignableFrom(typeClass); + // } + // catch (Throwable e) { + // // ignore this and fall back to JDT does implement checks + // } + // } + // return doesImplementWithJdt(resource, type, className); + // } + + // public static IType getAjdtType(IProject project, String className) { + // IJavaProject javaProject = getJavaProject(project); + // if (IS_AJDT_PRESENT && javaProject != null && className != null) { + // + // try { + // IType type = null; + // + // // First look for the type in the project + // if (isAjdtProject(project)) { + // type = AjdtUtils.getAjdtType(project, className); + // if (type != null) { + // return type; + // } + // } + // + // // Then look for the type in the referenced Java projects + // for (IProject refProject : project.getReferencedProjects()) { + // if (isAjdtProject(refProject)) { + // type = AjdtUtils.getAjdtType(refProject, className); + // if (type != null) { + // return type; + // } + // } + // } + // } + // catch (CoreException e) { + // StatusHandler.log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, + // "Error getting Java type '" + // + className + "'", e)); + // } + // } + // return null; + // } + + public static List getAllDependingJavaProjects(IJavaProject project) { + List javaProjects = new ArrayList(); + IJavaModel model = JavaCore.create(ResourcesPlugin.getWorkspace().getRoot()); + if (model != null) { + try { + String[] names = project.getRequiredProjectNames(); + IJavaProject[] projects = model.getJavaProjects(); + for (IJavaProject project2 : projects) { + for (String name2 : names) { + String name = project2.getProject().getName(); + if (name.equals(name2)) { + javaProjects.add(project2); + } + } + } + } + catch (JavaModelException exception) { + } + } + return javaProjects; + } + + /** + * Creates a Set of {@link URL}s from the OSGi bundle class path manifest + * entry. + */ + // public static Set getBundleClassPath(String bundleId) { + // Set paths = new HashSet(); + // try { + // Bundle bundle = Platform.getBundle(bundleId); + // if (bundle != null) { + // String bundleClassPath = (String) bundle.getHeaders() + // .get(org.osgi.framework.Constants.BUNDLE_CLASSPATH); + // if (bundleClassPath != null) { + // String[] classPathEntries = + // StringUtils.delimitedListToStringArray(bundleClassPath, ","); + // for (String classPathEntry : classPathEntries) { + // if (".".equals(classPathEntry.trim())) { + // paths.add(FileLocator.toFileURL(bundle.getEntry("/"))); + // } + // else { + // paths.add(FileLocator.toFileURL(new URL(bundle.getEntry("/"), "/" + + // classPathEntry.trim()))); + // } + // } + // } + // else { + // paths.add(FileLocator.toFileURL(bundle.getEntry("/"))); + // } + // } + // } + // catch (MalformedURLException e) { + // StatusHandler + // .log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, + // "An error occurred getting classpath", e)); + // } + // catch (IOException e) { + // StatusHandler + // .log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, + // "An error occurred getting classpath", e)); + // } + // return paths; + // } + + /** + * @since 2.6.0 + */ + public static IJavaElement getByHandle(String handle) { + // if (IS_AJDT_PRESENT) { + // return AjdtUtils.getByHandle(handle); + // } + // else { + return JavaCore.create(handle); + // } + } + + /** + * Create a {@link ClassLoader} from the class path configuration of the + * given project. + * @param project the {@link IProject} + * @param useParentClassLoader true if the current OSGi class loader should + * be used as parent class loader for the constructed class loader. + * @return {@link ClassLoader} instance constructed from the + * project's build path configuration + */ + // public static ClassLoader getClassLoader(IProject project, ClassLoader + // parentClassLoader) { + // return ProjectClassLoaderCache.getClassLoader(project, + // parentClassLoader); + // } + // + // public static void removeClassLoaderEntryFromCache(IProject project) { + // ProjectClassLoaderCache.removeClassLoaderEntryFromCache(project); + // } + // + // public static IMethod getConstructor(IType type, Class[] parameterTypes) + // { + // String[] parameterTypesAsString = + // getParameterTypesAsStringArray(parameterTypes); + // return getConstructor(type, parameterTypesAsString); + // } + + // public static IMethod getConstructor(IType type, String[] parameterTypes) + // { + // try { + // Set methods = Introspector.getAllConstructors(type); + // for (IMethod method : methods) { + // if (method.getParameterTypes().length == parameterTypes.length) { + // String[] methodParameterTypes = getParameterTypesAsStringArray(method); + // if (Arrays.deepEquals(parameterTypes, methodParameterTypes)) { + // return method; + // } + // } + // } + // } + // catch (JavaModelException e) { + // } + // return null; + // } + + // public static IField getField(IType type, String fieldName, String + // parameterType) { + // try { + // Set methods = Introspector.getAllFields(type); + // for (IField field : methods) { + // if (field.getElementName().equals(fieldName)) { + // return field; + // } + // } + // } + // catch (JavaModelException e) { + // } + // return null; + // } + + /** + * Returns a flat list of all interfaces and super types for the given + * {@link IType}. + */ + // public static List getFlatListOfClassAndInterfaceNames(IType + // parameterType, IType type) { + // List requiredTypes = new ArrayList(); + // if (parameterType != null) { + // do { + // try { + // requiredTypes.add(parameterType.getFullyQualifiedName()); + // String[] interfaceNames = parameterType.getSuperInterfaceNames(); + // for (String interfaceName : interfaceNames) { + // if (interfaceName != null) { + // if (type.isBinary()) { + // requiredTypes.add(interfaceName); + // } + // String resolvedName = resolveClassName(interfaceName, type); + // if (resolvedName != null) { + // requiredTypes.add(resolvedName); + // } + // } + // } + // parameterType = Introspector.getSuperType(parameterType); + // } + // catch (JavaModelException e) { + // } + // } while (parameterType != null && + // !parameterType.getFullyQualifiedName().equals(Object.class.getName())); + // } + // return requiredTypes; + // } + + /** + * Returns the corresponding Java project or null a for given + * project. + * @param project the project the Java project is requested for + * @return the requested Java project or null if the Java + * project is not defined or the project is not accessible + */ + public static IJavaProject getJavaProject(IProject project) { + if (project.isAccessible()) { + try { + if (project.hasNature(JavaCore.NATURE_ID)) { + return (IJavaProject) project.getNature(JavaCore.NATURE_ID); + } + } + catch (CoreException e) { + StatusHandler.log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, + "Error getting Java project for project '" + project.getName() + "'", e)); + } + } + return null; + } + + public static IJavaProject getJavaProject(IResource config) { + IJavaProject project = JavaCore.create(config.getProject()); + return project; + } + + /** + * Returns the corresponding Java type for given full-qualified class name. + * @param project the JDT project the class belongs to + * @param className the full qualified class name of the requested Java type + * @return the requested Java type or null if the class is not defined or + * the project is not accessible + */ + public static IType getJavaType(IProject project, String className) { + IJavaProject javaProject = JdtUtils.getJavaProject(project); + if (className != null) { + // For inner classes replace '$' by '.' + int pos = className.lastIndexOf('$'); + if (pos > 0) { + className = className.replace('$', '.'); + } + try { + IType type = null; + // First look for the type in the Java project + if (javaProject != null) { + // TODO CD not sure why we need + type = javaProject.findType(className, new NullProgressMonitor()); + // type = javaProject.findType(className); + if (type != null) { + return type; + } + } + + // Then look for the type in the referenced Java projects + for (IProject refProject : project.getReferencedProjects()) { + IJavaProject refJavaProject = JdtUtils.getJavaProject(refProject); + if (refJavaProject != null) { + type = refJavaProject.findType(className); + if (type != null) { + return type; + } + } + } + + // fall back and try to locate the class using AJDT + // TODO: uncomment this call + // return getAjdtType(project, className); + } + catch (CoreException e) { + StatusHandler.log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "Error getting Java type '" + + className + "'", e)); + } + } + + return null; + } + + public static final IType getJavaTypeForMethodReturnType(IMethod method, IType contextType) { + try { + return JdtUtils.getJavaTypeFromSignatureClassName(method.getReturnType(), contextType); + } + catch (JavaModelException e) { + } + return null; + } + + public static IType getJavaTypeFromSignatureClassName(String className, IType contextType) { + if (contextType == null || className == null) { + return null; + } + try { + return JdtUtils.getJavaType(contextType.getJavaProject().getProject(), + JdtUtils.resolveClassNameBySignature(className, contextType)); + } + catch (IllegalArgumentException e) { + // do Nothing + } + return null; + } + + @SuppressWarnings("unchecked") + public static final List getJavaTypesForMethodParameterTypes(IMethod method, IType contextType) { + if (method == null || method.getParameterTypes() == null || method.getParameterTypes().length == 0) { + return Collections.EMPTY_LIST; + } + List parameterTypes = new ArrayList(method.getParameterTypes().length); + String[] parameterTypeNames = method.getParameterTypes(); + for (String parameterTypeName : parameterTypeNames) { + parameterTypes.add(JdtUtils.getJavaTypeFromSignatureClassName(parameterTypeName, contextType)); + } + return parameterTypes; + } + + public static IClasspathEntry getJreVariableEntry() { + return JavaRuntime.getDefaultJREContainerEntry(); + } + + public static int getLineNumber(IJavaElement element) { + if (element != null && element instanceof IMethod) { + try { + IMethod method = (IMethod) element; + int lines = 0; + if (method.getDeclaringType() != null && method.getDeclaringType().getCompilationUnit() != null) { + String targetsource = method.getDeclaringType().getCompilationUnit().getSource(); + if (targetsource != null) { + String sourceuptomethod = targetsource.substring(0, method.getNameRange().getOffset()); + + char[] chars = new char[sourceuptomethod.length()]; + sourceuptomethod.getChars(0, sourceuptomethod.length(), chars, 0); + for (char element0 : chars) { + if (element0 == '\n') { + lines++; + } + } + return new Integer(lines + 1); + } + } + } + catch (JavaModelException e) { + } + } + else if (element != null && element instanceof IType && ((IType) element).getCompilationUnit() != null) { + try { + IType type = (IType) element; + int lines = 0; + String targetsource = type.getCompilationUnit().getSource(); + if (targetsource != null) { + String sourceuptomethod = targetsource.substring(0, type.getNameRange().getOffset()); + + char[] chars = new char[sourceuptomethod.length()]; + sourceuptomethod.getChars(0, sourceuptomethod.length(), chars, 0); + for (char element0 : chars) { + if (element0 == '\n') { + lines++; + } + } + return new Integer(lines + 1); + } + } + catch (JavaModelException e) { + } + } + else if (element != null && element instanceof IField) { + try { + IField type = (IField) element; + int lines = 0; + ICompilationUnit cu = type.getCompilationUnit(); + if (cu != null) { + String targetsource = cu.getSource(); + if (targetsource != null) { + String sourceuptomethod = targetsource.substring(0, type.getNameRange().getOffset()); + + char[] chars = new char[sourceuptomethod.length()]; + sourceuptomethod.getChars(0, sourceuptomethod.length(), chars, 0); + for (char element0 : chars) { + if (element0 == '\n') { + lines++; + } + } + return new Integer(lines + 1); + } + } + } + catch (JavaModelException e) { + } + } + return new Integer(-1); + } + + // public static IMethod getMethod(IType type, String methodName, Class[] + // parameterTypes) { + // String[] parameterTypesAsString = + // getParameterTypesAsStringArray(parameterTypes); + // return getMethod(type, methodName, parameterTypesAsString); + // } + + // public static IMethod getMethod(IType type, String methodName, String[] + // parameterTypes) { + // int index = methodName.indexOf('('); + // if (index >= 0) { + // methodName = methodName.substring(0, index); + // } + // try { + // Set methods = Introspector.getAllMethods(type); + // for (IMethod method : methods) { + // if (method.getElementName().equals(methodName) + // && method.getParameterTypes().length == parameterTypes.length) { + // String[] methodParameterTypes = getParameterTypesAsStringArray(method); + // if (Arrays.deepEquals(parameterTypes, methodParameterTypes)) { + // return method; + // } + // } + // } + // + // return Introspector.findMethod(type, methodName, parameterTypes.length, + // Public.YES, Static.DONT_CARE); + // } + // catch (JavaModelException e) { + // } + // return null; + // } + + public static String getMethodName(IMethod method) { + // Special support Ajdt intertype declarations + String methodName = method.getElementName(); + int index = methodName.lastIndexOf('.'); + if (index > 0) { + methodName = methodName.substring(index + 1); + } + return methodName; + } + + public static String[] getParameterTypesString(IMethod method) { + try { + String[] parameterQualifiedTypes = Signature.getParameterTypes(method.getSignature()); + int length = parameterQualifiedTypes == null ? 0 : parameterQualifiedTypes.length; + String[] parameterPackages = new String[length]; + for (int i = 0; i < length; i++) { + parameterQualifiedTypes[i] = parameterQualifiedTypes[i].replace('/', '.'); + parameterPackages[i] = Signature.getSignatureSimpleName(parameterQualifiedTypes[i]); + } + return parameterPackages; + } + catch (IllegalArgumentException e) { + } + catch (JavaModelException e) { + } + return null; + } + + public static String getParentName(IMethod method) { + // Special support Ajdt intertype declarations + String methodName = method.getElementName(); + int index = methodName.lastIndexOf('.'); + if (index > 0) { + return methodName.substring(0, index); + } + else { + return method.getParent().getElementName(); + } + } + + // public static IProjectClassLoaderSupport + // getProjectClassLoaderSupport(IProject je, ClassLoader parentClassLoader) + // { + // return new DefaultProjectClassLoaderSupport(je, parentClassLoader); + // } + + public static String getPropertyNameFromMethodName(IMethod method) { + // Special support Ajdt intertype declarations + String methodName = method.getElementName(); + int index = methodName.lastIndexOf('.'); + if (index > 0) { + methodName = methodName.substring(index + 1); + } + String replaceText = methodName.substring("set".length()); + if (replaceText != null) { + replaceText = java.beans.Introspector.decapitalize(replaceText); + } + return replaceText; + } + + public static String getReturnTypeString(IMethod method, boolean classTypesOnly) { + try { + String qualifiedReturnType = Signature.getReturnType(method.getSignature()); + if (!classTypesOnly || qualifiedReturnType.startsWith("L") || qualifiedReturnType.startsWith("Q")) { + return Signature.getSignatureSimpleName(qualifiedReturnType.replace('/', '.')); + } + } + catch (IllegalArgumentException e) { + } + catch (JavaModelException e) { + } + return null; + } + + public static IResource getSourceResource(IResource classFile) { + try { + if (isJavaProject(classFile) && classFile.getName().endsWith(CLASS_FILE_EXTENSION)) { + IPath classFilePath = classFile.getFullPath(); + String classFileName = null; + + IJavaProject project = getJavaProject(classFile); + IPath defaultOutput = project.getOutputLocation(); + + if (defaultOutput.isPrefixOf(classFilePath)) { + classFileName = classFilePath.removeFirstSegments(defaultOutput.segmentCount()).toString(); + } + else { + for (IClasspathEntry entry : project.getRawClasspath()) { + if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { + IPath output = entry.getOutputLocation(); + if (output != null) { + if (classFilePath.isPrefixOf(output)) { + classFileName = classFilePath.removeFirstSegments(output.segmentCount()).toString(); + } + } + } + } + } + + if (classFileName != null) { + // Replace file extension + String sourceFileName = classFileName.replace(".class", ".java"); + for (IClasspathEntry entry : project.getRawClasspath()) { + if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { + IPath path = entry.getPath().append(sourceFileName).removeFirstSegments(1); + IResource resource = project.getProject().findMember(path); + if (resource != null) { + return resource; + } + } + } + } + } + } + catch (JavaModelException e) { + } + return null; + } + + public static boolean isAjdtPresent() { + try { + Class.forName(AJDT_CLASS); + return true; + } + catch (ClassNotFoundException e) { + return false; + } + } + + /** + * Returns true if given resource's project is a ADJT project. + */ + public static boolean isAjdtProject(IResource resource) { + return SpringCoreUtils.hasNature(resource, AJDT_NATURE); + } + + /** + * Determines if the resource under question is the .classpath + * file of a {@link IJavaProject}. + */ + public static boolean isClassPathFile(IResource resource) { + String classPathFileName = resource.getProject().getFullPath().append(CLASSPATH_FILENAME).toString(); + return resource.getFullPath().toString().equals(classPathFileName); + } + + /** + * Returns true if given resource's project is a Java project. + */ + public static boolean isJavaProject(IResource resource) { + return SpringCoreUtils.hasNature(resource, JavaCore.NATURE_ID); + } + + // public static boolean isTypeAjdtElement(IType type) { + // if (IS_AJDT_PRESENT) { + // return AjdtUtils.isTypeAjdtElement(type); + // } + // return false; + // } + + public static boolean isTypeGroovyElement(IType type) { + // TODO CD verify following check with Groovy Eclipse + ICompilationUnit cu = type.getCompilationUnit(); + if (cu != null && cu.getResource() != null) { + return cu.getResource().getName().endsWith(GROOVY_FILE_EXTENSION); + } + else if (cu != null) { + try { + IResource resource = cu.getUnderlyingResource(); + if (resource != null) { + return resource.getName().endsWith(GROOVY_FILE_EXTENSION); + } + } + catch (JavaModelException e) { + // ignore + } + } + return false; + } + + public static String resolveClassName(String className, IType type) { + if (className == null || type == null) { + return className; + } + // replace binary $ inner class name syntax with . for source level + className = className.replace('$', '.'); + String dotClassName = new StringBuilder().append('.').append(className).toString(); + + IProject project = type.getJavaProject().getProject(); + + try { + // Special handling for some well-know classes + if (className.startsWith("java.lang") && getJavaType(project, className) != null) { + return className; + } + + // Check if the class is imported + if (!type.isBinary()) { + + // Strip className to first segment to support + // ReflectionUtils.MethodCallback + int ix = className.lastIndexOf('.'); + String firstClassNameSegment = className; + if (ix > 0) { + firstClassNameSegment = className.substring(0, ix); + } + + // Iterate the imports + for (IImportDeclaration importDeclaration : type.getCompilationUnit().getImports()) { + String importName = importDeclaration.getElementName(); + // Wildcard imports -> check if the package + className is a + // valid type + if (importDeclaration.isOnDemand()) { + String newClassName = new StringBuilder(importName.substring(0, importName.length() - 1)) + .append(className).toString(); + if (getJavaType(project, newClassName) != null) { + return newClassName; + } + } + // Concrete import matching .className at the end -> check + // if type exists + else if (importName.endsWith(dotClassName) && getJavaType(project, importName) != null) { + return importName; + } + // Check if className is multi segmented + // (ReflectionUtils.MethodCallback) + // -> check if the first segment + else if (!className.equals(firstClassNameSegment)) { + if (importName.endsWith(firstClassNameSegment)) { + String newClassName = new StringBuilder(importName.substring(0, + importName.lastIndexOf('.') + 1)).append(className).toString(); + if (getJavaType(project, newClassName) != null) { + return newClassName; + } + } + } + } + } + + // Check if the class is in the same package as the type + String packageName = type.getPackageFragment().getElementName(); + String newClassName = new StringBuilder(packageName).append(dotClassName).toString(); + if (getJavaType(project, newClassName) != null) { + return newClassName; + } + + // Check if the className is sufficient (already fully-qualified) + if (getJavaType(project, className) != null) { + return className; + } + + // Check if the class is coming from the java.lang + newClassName = new StringBuilder("java.lang").append(dotClassName).toString(); + if (getJavaType(project, newClassName) != null) { + return newClassName; + } + + // Fall back to full blown resolution + String[][] fullInter = type.resolveType(className); + if (fullInter != null && fullInter.length > 0) { + return fullInter[0][0] + "." + fullInter[0][1]; + } + } + catch (JavaModelException e) { + StatusHandler.log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "An error occurred resolving class name", + e)); + } + + return className; + } + + public static String resolveClassNameBySignature(String className, IType type) { + className = Signature.toString(className).replace('$', '.'); + return resolveClassName(className, type); + } + + public static void visitTypeAst(IType type, ASTVisitor visitor) { + if (type != null && type.getCompilationUnit() != null) { + ASTParser parser = ASTParser.newParser(AST.JLS3); + parser.setSource(type.getCompilationUnit()); + parser.setResolveBindings(true); + ASTNode node = parser.createAST(new NullProgressMonitor()); + node.accept(visitor); + } + } + + // private static boolean doesImplementWithJdt(IResource resource, IType + // type, String className) { + // IType interfaceType = getJavaType(resource.getProject(), className); + // if (type != null && interfaceType != null) { + // try { + // IType[] subTypes = + // SuperTypeHierarchyCache.getTypeHierarchy(interfaceType) + // .getAllSubtypes(interfaceType); + // if (subTypes != null) { + // for (IType subType : subTypes) { + // if (subType.equals(type)) { + // return true; + // } + // } + // } + // } + // catch (JavaModelException ex) { + // StatusHandler.log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, + // "An error occurred resolving subtypes", ex)); + // } + // } + // return false; + // } + + // private static String[] getParameterTypesAsStringArray(Class[] + // parameterTypes) { + // String[] parameterTypesAsString = new String[parameterTypes.length]; + // for (int i = 0; i < parameterTypes.length; i++) { + // parameterTypesAsString[i] = + // ClassUtils.getQualifiedName(parameterTypes[i]); + // } + // return parameterTypesAsString; + // } + + private static String[] getParameterTypesAsStringArray(IMethod method) { + Set typeParameterNames = new HashSet(); + try { + for (ITypeParameter param : method.getDeclaringType().getTypeParameters()) { + typeParameterNames.add(param.getElementName()); + } + for (ITypeParameter param : method.getTypeParameters()) { + typeParameterNames.add(param.getElementName()); + } + } + catch (JavaModelException e) { + } + String[] parameterTypesAsString = new String[method.getParameterTypes().length]; + for (int i = 0; i < method.getParameterTypes().length; i++) { + String parameterTypeString = Signature.getElementType(method.getParameterTypes()[i]); + boolean isArray = !parameterTypeString.equals(method.getParameterTypes()[i]); + + String parameterType = resolveClassNameBySignature(parameterTypeString, method.getDeclaringType()); + if (typeParameterNames.contains(parameterType)) { + parameterTypesAsString[i] = Object.class.getName() + (isArray ? ARRAY_SUFFIX : ""); + } + else { + parameterTypesAsString[i] = parameterType + (isArray ? ARRAY_SUFFIX : ""); + } + } + return parameterTypesAsString; + } + + // static class DefaultProjectClassLoaderSupport implements + // IProjectClassLoaderSupport { + // + // private ClassLoader classLoader; + // + // private ClassLoader weavingClassLoader; + // + // public DefaultProjectClassLoaderSupport(IProject javaProject, ClassLoader + // parentClassLoader) { + // setupClassLoaders(javaProject, parentClassLoader); + // } + // + // public void executeCallback(IProjectClassLoaderAwareCallback callback) + // throws Throwable { + // try { + // activateWeavingClassLoader(); + // callback.doWithActiveProjectClassLoader(); + // } + // finally { + // recoverClassLoader(); + // } + // } + // + // public ClassLoader getProjectClassLoader() { + // return this.weavingClassLoader; + // } + // + // /** + // * Activates the weaving class loader as thread context classloader. + // *

+ // * Use {@link #recoverClassLoader()} to recover the original thread + // * context classloader + // */ + // private void activateWeavingClassLoader() { + // Thread.currentThread().setContextClassLoader(weavingClassLoader); + // } + // + // private void recoverClassLoader() { + // Thread.currentThread().setContextClassLoader(classLoader); + // } + // + // private void setupClassLoaders(IProject project, ClassLoader + // parentClassLoader) { + // classLoader = Thread.currentThread().getContextClassLoader(); + // weavingClassLoader = ProjectClassLoaderCache.getClassLoader(project, + // parentClassLoader); + // } + // } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/MarkerUtils.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/MarkerUtils.java new file mode 100644 index 000000000..6669315f7 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/MarkerUtils.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 Spring IDE Developers + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Spring IDE Developers - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; + +/** + * Some {@link IMarker} helper methods. + * @author Torsten Juergeleit + * @author Christian Dupuis + */ +public final class MarkerUtils { + + public static final String ORIGINATING_RESOURCE_KEY = "originatingResource"; + + public static final String ELEMENT_ID_KEY = "elementId"; + + public static int getHighestSeverityFromMarkersInRange(IResource resource, String markerId, + int startLine, int endLine) { + int severity = -1; + if (resource != null) { + for (IMarker marker : getAllMarkersInRange(resource, markerId, startLine, endLine)) { + int sev = marker.getAttribute(IMarker.SEVERITY, -1); + if (sev == IMarker.SEVERITY_WARNING) { + severity = sev; + } + else if (sev == IMarker.SEVERITY_ERROR) { + severity = sev; + break; + } + } + } + return severity; + } + + public static Set getAllMarkersInRange(IResource resource, String markerId, int startLine, int endLine) { + Set foundMarkers = new HashSet(); + if (resource != null) { + try { + IMarker[] markers = resource.findMarkers(markerId, true, + IResource.DEPTH_INFINITE); + for (IMarker marker : markers) { + if (startLine == -1 || isMarkerInRange(marker, startLine, endLine)) { + foundMarkers.add(marker); + } + } + } + catch (CoreException e) { + // ignore + } + } + return foundMarkers; + } + + public static boolean isMarkerInRange(IMarker marker, int startLine, int endLine) + throws CoreException { + if (startLine >= 0 && endLine >= startLine && marker.isSubtypeOf(IMarker.TEXT)) { + int line = marker.getAttribute(IMarker.LINE_NUMBER, -1); + return (line >= startLine && line <= endLine); + } + return false; + } + + /** + * Removes all {@link IMarker markers} with the given id (including the + * inherited ones) from given {@link IResource} and it's members that if the + * marker has an attribute under the key named + * {@link #ORIGINATING_RESOURCE_KEY} attribute that matches the given + * {@link IResource#getFullPath()}. + */ + public static void deleteAllMarkers(IResource resource, String id) { + if (resource != null && resource.isAccessible()) { + try { + IMarker[] markers = resource.findMarkers(id, true, + IResource.DEPTH_ONE); + for (IMarker marker : markers) { +// if (originatingResourceValue.equals(marker +// .getAttribute(ORIGINATING_RESOURCE_KEY))) { + marker.delete(); +// } + } + } + catch (CoreException e) { + CorePlugin.log(e); + } + } + } + + /** + * Removes all {@link IMarker markers} with given id (including the + * inherited ones) from given {@link IResource} and it's members. + */ + public static void deleteMarkers(IResource resource, String id) { + if (resource != null && resource.isAccessible()) { + try { + resource.deleteMarkers(id, true, IResource.DEPTH_INFINITE); + } + catch (CoreException e) { + CorePlugin.log(e); + } + } + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/PatternUtils.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/PatternUtils.java new file mode 100644 index 000000000..23aefe009 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/PatternUtils.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 Spring IDE Developers + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Spring IDE Developers - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * @author Torsten Juergeleit + * @author Christian Dupuis + */ +public final class PatternUtils { + + private PatternUtils() { + // Not for instantiation + } + + /** + * Creates a pattern element from the pattern string which is either a + * reg-ex expression or of wildcard format ('*' matches any character and + * '?' matches one character). + * @param pattern the search pattern + * @param isCaseSensitive set to true to create a case + * insensitve pattern + * @param isRegexSearch true if the passed string is a reg-ex + * pattern + * @throws PatternSyntaxException + */ + public static Pattern createPattern(String pattern, + boolean isCaseSensitive, boolean isRegexSearch) + throws PatternSyntaxException { + if (!isRegexSearch) { + pattern = toRegExFormat(pattern); + } + if (!isCaseSensitive) { + return Pattern.compile(pattern, Pattern.CASE_INSENSITIVE | + Pattern.UNICODE_CASE | Pattern.MULTILINE); + } + return Pattern.compile(pattern, Pattern.MULTILINE); + } + + /** + * Converts wildcard format ('*' and '?') to reg-ex format. + */ + private static String toRegExFormat(String pattern) { + StringBuffer regex = new StringBuffer(pattern.length()); + boolean escaped = false; + boolean quoting = false; + for (int i = 0; i < pattern.length(); i++) { + char c = pattern.charAt(i); + if (c == '*' && !escaped) { + if (quoting) { + regex.append("\\E"); + quoting = false; + } + regex.append(".*"); + escaped = false; + continue; + } else if (c == '?' && !escaped) { + if (quoting) { + regex.append("\\E"); + quoting = false; + } + regex.append("."); + escaped = false; + continue; + } else if (c == '\\' && !escaped) { + escaped = true; + continue; + } else if (c == '\\' && escaped) { + escaped = false; + if (quoting) { + regex.append("\\E"); + quoting = false; + } + regex.append("\\\\"); + continue; + } + if (!quoting) { + regex.append("\\Q"); + quoting = true; + } + if (escaped && c != '*' && c != '?' && c != '\\') { + regex.append('\\'); + } + regex.append(c); + escaped = c == '\\'; + } + if (quoting) { + regex.append("\\E"); + } + return regex.toString(); + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/Policy.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/Policy.java new file mode 100644 index 000000000..1901ec304 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/Policy.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; + +/** + * @author Steffen Pingel + * @author Leo Dos Santos + * @author Christian Dupuis + */ +public class Policy { + + public static boolean TEST_MODE; + + static { + String application = System.getProperty("eclipse.application", ""); //$NON-NLS-1$ //$NON-NLS-2$ + if (application.length() > 0) { + TEST_MODE = application.endsWith("testapplication") || application.endsWith("uitest"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + + public static void checkCancelled(IProgressMonitor monitor) { + if (monitor != null && monitor.isCanceled()) { + throw new OperationCanceledException(); + } + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/ResourceProvider.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/ResourceProvider.java new file mode 100644 index 000000000..91ea6ae23 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/ResourceProvider.java @@ -0,0 +1,344 @@ +/******************************************************************************* + * Copyright (c) 2012 - 2014 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtension; +import org.eclipse.core.runtime.IExtensionPoint; +import org.eclipse.core.runtime.IExtensionRegistry; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.preferences.DefaultScope; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.Bundle; +import org.osgi.framework.Version; +import org.osgi.service.prefs.BackingStoreException; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; + +/** + * Provides access to externalized resources such as urls. Resources are + * specified through an extension point. + * + * @author Steffen Pingel + * @author Christian Dupuis + */ +public class ResourceProvider { + + public static final String RECORD_SEPARATOR_PATTERN = "[\\r\\n\\s]"; + + public static final String FIELD_SEPARATOR = "|"; + + public static final String FIELD_SEPARATOR_PATTERN = "\\|"; + + public static final String RECORD_SEPARATOR = "\n"; + + public static class Property { + + public String type; + + private String id; + + private String label; + + private String defaultValue; + + private boolean multiValue; + + private boolean userConfigurable; + + private String contributedValue; + + private final ResourceProvider provider; + + private Property(ResourceProvider provider) { + this.provider = provider; + } + + public String getDefaultValue() { + String value = contributedValue; + if (value == null) { + value = defaultValue; + } + return value; + } + + public String getLabel() { + return label; + } + + public String getValue() { + String value = provider.getPreferences().get(id, null); + if (value == null) { + value = contributedValue; + } + if (value == null) { + value = defaultValue; + } + return value; + } + + public boolean isMultiValue() { + return multiValue; + } + + public boolean isUserConfigurable() { + return userConfigurable; + } + + public void setValue(String value) { + provider.getPreferences().put(id, value); + try { + provider.getPreferences().flush(); + } catch (BackingStoreException e) { + StatusHandler.log(new Status(IStatus.ERROR, + CorePlugin.PLUGIN_ID, + "Error occured while saving preferences", e)); + } + } + + } + + private static class ExtensionPointReader { + + private static final String ATTRIBUTE_ID = "id"; + + private static final String ATTRIBUTE_VALUE = "value"; + + private static final String ATTRIBUTE_LABEL = "label"; + + private static final String ATTRIBUTE_TYPE = "type"; + + private static final String ATTRIBUTE_MULTI_VALUE = "multiValue"; + + private static final String ATTRIBUTE_DEFAULT_VALUE = "defaultValue"; + + private static final String ATTRIBUTE_USER_CONFIGURABLE = "userConfigurable"; + + private static final String ELEMENT_URL = "url"; + + private static final String ELEMENT_PROPERTY = "propertyDefinition"; + + private static final String EXTENSION_ID_REFERENCES = "com.springsource.sts.core.resources"; + + private final Map valueByKey = new HashMap(); + + private final Map propertyById = new HashMap(); + + public void read(ResourceProvider resourceProvider) { + IExtensionRegistry registry = Platform.getExtensionRegistry(); + IExtensionPoint extensionPoint = registry + .getExtensionPoint(EXTENSION_ID_REFERENCES); + IExtension[] extensions = extensionPoint.getExtensions(); + + // read property definitions + for (IExtension extension : extensions) { + IConfigurationElement[] elements = extension + .getConfigurationElements(); + for (IConfigurationElement element : elements) { + if (element.getName().compareTo(ELEMENT_PROPERTY) == 0) { + Property p = new Property(resourceProvider); + p.id = element.getAttribute(ATTRIBUTE_ID); + p.label = element.getAttribute(ATTRIBUTE_LABEL); + p.defaultValue = element + .getAttribute(ATTRIBUTE_DEFAULT_VALUE); + p.multiValue = Boolean.parseBoolean(element + .getAttribute(ATTRIBUTE_MULTI_VALUE)); + p.userConfigurable = Boolean.parseBoolean(element + .getAttribute(ATTRIBUTE_USER_CONFIGURABLE)); + p.type = element.getAttribute(ATTRIBUTE_TYPE); + propertyById.put(p.id, p); + } + } + } + + // read contributed definitions + for (IExtension extension : extensions) { + IConfigurationElement[] elements = extension + .getConfigurationElements(); + for (IConfigurationElement element : elements) { + if (element.getName().compareTo(ELEMENT_URL) == 0) { + String key = element.getAttribute(ATTRIBUTE_ID); + String value = element.getAttribute(ATTRIBUTE_VALUE); + valueByKey.put(key, value); + + Property p = propertyById.get(key); + if (p == null) { + // property was not defined explicitly + p = new Property(resourceProvider); + p.id = key; + p.label = ""; + propertyById.put(p.id, p); + } + p.contributedValue = value; + } + } + } + } + + } + + private final IEclipsePreferences preferences; + + private static ResourceProvider instance = new ResourceProvider(); + + public static ResourceProvider getInstance() { + return instance; + } + + public static String getUrl(String key) { + return getUrls(key)[0]; + } + + public static String[] getUrls(String key) { + String[] urls = parseUrls(key, instance.getValue(key)); + String[] defaultUrls = parseUrls(key, instance.getDefaultValue(key)); + + Set cleanableUrls = new HashSet<>(); + for (String url : defaultUrls) { + if (url.startsWith("https:")) { + cleanableUrls.add("http:"+url.substring(6)); + } else if (url.startsWith("http:")) { + cleanableUrls.add(url); + } + } + + for (int i = 0; i < urls.length; i++) { + String url = urls[i]; + if (cleanableUrls.contains(url)) { + urls[i] = "https:"+url.substring(5); + } + } + return urls; + } + + private static String[] parseUrls(String key, String value) { + if (value == null) { + throw new RuntimeException(NLS.bind( + "No URL found for key: ''{0}''", key)); + } + if (value.isEmpty()) { + return new String[0]; + } + + // Post process the urls to replace UUID and version number placeholders + if (value.contains("%UUID%")) { + value = value.replace("%UUID%", CorePlugin.getUUID()); + } + if (value.contains("%VERSION%") || value.contains("%SHORT_VERSION%")) { + Bundle bundle = CorePlugin.getDefault().getBundle(); + Version version = new Version((String) bundle.getHeaders().get( + "Bundle-Version")); //$NON-NLS-1$ + value = value.replace("%VERSION%", version.toString()); + value = value.replace("%SHORT_VERSION%", version.getMajor() + "." + + version.getMinor()); + } + String[] values = value.split(RECORD_SEPARATOR_PATTERN); + return discardLocationNames(values); + } + + private static String[] discardLocationNames(String[] values) { + for (int i = 0; i < values.length; i++) { + String nameUrlString = values[i]; + String[] nameUrlStrings = nameUrlString + .split(FIELD_SEPARATOR_PATTERN); + if (nameUrlStrings.length == 2) { + String url = nameUrlStrings[1]; + values[i] = url; + } + } + return values; + } + + private final Map propertyById; + + private final List listeners; + + private ResourceProvider() { + this.listeners = new CopyOnWriteArrayList(); + this.preferences = new InstanceScope().getNode(CorePlugin.PLUGIN_ID); + this.preferences + .addPreferenceChangeListener(new IPreferenceChangeListener() { + public void preferenceChange(PreferenceChangeEvent event) { + Property p = propertyById.get(event.getKey()); + if (p != null) { + for (PropertyChangeListener listener : listeners) { + listener.propertyChange(new PropertyChangeEvent( + ResourceProvider.this, p.id, event + .getOldValue(), event + .getNewValue())); + } + } + } + }); + + DefaultScope.INSTANCE.getNode(CorePlugin.PLUGIN_ID).addPreferenceChangeListener(new IPreferenceChangeListener() { + @Override + public void preferenceChange(PreferenceChangeEvent event) { + Property p = propertyById.get(event.getKey()); + if (p != null) { + p.defaultValue = (String)event.getNewValue(); + } + } + }); + + ExtensionPointReader reader = new ExtensionPointReader(); + reader.read(this); + this.propertyById = reader.propertyById; + } + + public void addPropertyChangeListener(PropertyChangeListener listener) { + listeners.add(listener); + } + + public IEclipsePreferences getPreferences() { + return preferences; + } + + public Collection getProperties() { + return Collections.unmodifiableCollection(propertyById.values()); + } + + public Property getProperty(String id) { + return propertyById.get(id); + } + + public void removePropertyChangeListener(PropertyChangeListener listener) { + listeners.remove(listener); + } + + private String getValue(String key) { + Property p = propertyById.get(key); + return (p != null) ? p.getValue() : null; + } + + private String getDefaultValue(String key) { + Property p = propertyById.get(key); + return (p != null) ? p.getDefaultValue() : null; + } + + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/SpringCorePreferences.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/SpringCorePreferences.java new file mode 100644 index 000000000..1d52d2f89 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/SpringCorePreferences.java @@ -0,0 +1,104 @@ +// COPIED from spring-ide org.springframework.ide.eclipse.core.SpringCorePreferences +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ProjectScope; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IScopeContext; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.osgi.service.prefs.BackingStoreException; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; + + +/** + * @author Christian Dupuis + * @since 2.0 + */ +public class SpringCorePreferences { + + /** The identifier for enablement of project versus workspace settings */ + public static final String PROJECT_PROPERTY_ID = "enable.project.preferences"; + + private final String propertyNamespace; + + private final IEclipsePreferences preferences; + + private SpringCorePreferences(IProject project, String qualifier) { + this.propertyNamespace = qualifier + '.'; + this.preferences = getEclipsePreferences(project, qualifier); + } + + // public static SpringCorePreferences getProjectPreferences(IProject + // project) { + // return getProjectPreferences(project, SpringCore.PLUGIN_ID); + // } + + public static SpringCorePreferences getProjectPreferences(IProject project, String qualifier) { + return new SpringCorePreferences(project, qualifier); + } + + public static SpringCorePreferences getPluginPreferences(String qualifier) { + return new SpringCorePreferences(null, qualifier); + } + + private IEclipsePreferences getEclipsePreferences(IProject project, String qualifier) { + IScopeContext context = project!=null + ? new ProjectScope(project) + : InstanceScope.INSTANCE; + IEclipsePreferences node = context.getNode(qualifier); + return node; + } + + public void putString(String key, String value) { + if (key == null || value == null) { + return; + } + try { + this.preferences.put(propertyNamespace + key, value); + this.preferences.flush(); + } + catch (BackingStoreException e) { + StatusHandler.log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "An error occurred updating preferences", + e)); + } + } + + public void putBoolean(String key, boolean value) { + if (key == null) { + return; + } + try { + this.preferences.putBoolean(propertyNamespace + key, value); + this.preferences.flush(); + } + catch (BackingStoreException e) { + StatusHandler.log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "An error occurred updating preferences", + e)); + } + } + + public String getString(String key, String defaultValue) { + return this.preferences.get(propertyNamespace + key, defaultValue); + } + + public boolean getBoolean(String key, boolean defaultValue) { + return this.preferences.getBoolean(propertyNamespace + key, defaultValue); + } + + public IEclipsePreferences getProjectPreferences() { + return this.preferences; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/SpringCoreUtils.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/SpringCoreUtils.java new file mode 100644 index 000000000..0b68a1cd3 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/SpringCoreUtils.java @@ -0,0 +1,809 @@ +// COPIED from spring-ide org.springframework.ide.eclipse.core.SpringCoreUtils +/******************************************************************************* + * Copyright (c) 2012, 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.util.LinkedHashSet; +import java.util.Set; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.core.resources.ICommand; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IPathVariableManager; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.IncrementalProjectBuilder; +import org.eclipse.core.resources.ProjectScope; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.preferences.IScopeContext; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.wst.common.project.facet.core.FacetedProjectFramework; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.Version; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Some helper methods. + * @author Torsten Juergeleit + * @author Christian Dupuis + * @author Martin Lippert + */ +public final class SpringCoreUtils { + + /** + * File name of OSGi bundle manifests + * @since 2.0.5 + */ + public static String BUNDLE_MANIFEST_FILE = "MANIFEST.MF"; + + /** + * Folder name of OSGi bundle manifests directories + * @since 2.0.5 + */ + public static String BUNDLE_MANIFEST_FOLDER = "META-INF"; + + /** New placeholder string for Spring 3 EL support */ + public static final String EL_PLACEHOLDER_PREFIX = "#{"; + + /** URL file schema */ + public static final String FILE_SCHEME = "file"; + + public static final String LINE_SEPARATOR = System.getProperty("line.separator"); + + public static final String PLACEHOLDER_PREFIX = "${"; + + public static final String PLACEHOLDER_SUFFIX = "}"; + + public static final String SOURCE_CONTROL_SCHEME = "sourcecontrol"; + + private static final String DEPLOY_PATH = "deploy-path"; + + private static boolean DOCUMENT_BUILDER_ERROR = false; + + private static final Object DOCUMENT_BUILDER_LOCK = new Object(); + + private static XPathExpression EXPRESSION; + + private static boolean SAX_PARSER_ERROR = false; + + private static final Object SAX_PARSER_LOCK = new Object(); + + private static final String SOURCE_PATH = "source-path"; + + private static final String XPATH_EXPRESSION = "//project-modules/wb-module/wb-resource"; + + private static final String SPRING_BUILDER_ID = "org.springframework.ide.eclipse.core.springbuilder"; + + private static final String MARKER_ID = "org.springframework.ide.eclipse.core.problemmarker"; + + public static final String NATURE_ID = "org.springframework.ide.eclipse.core.springnature"; + + static { + try { + XPathFactory newInstance = XPathFactory.newInstance(); + XPath xpath = newInstance.newXPath(); + EXPRESSION = xpath.compile(XPATH_EXPRESSION); + } + catch (XPathExpressionException e) { + throw new RuntimeException(e); + } + } + + /** + * Adds given builder to specified project. + */ + public static void addProjectBuilder(IProject project, String builderID, IProgressMonitor monitor) + throws CoreException { + IProjectDescription desc = project.getDescription(); + ICommand builderCommand = getProjectBuilderCommand(desc, builderID); + if (builderCommand == null) { + + // Add a new build spec + ICommand command = desc.newCommand(); + command.setBuilderName(builderID); + + // Commit the spec change into the project + addProjectBuilderCommand(desc, command); + project.setDescription(desc, monitor); + } + } + + /** + * Adds (or updates) a builder in given project description. + */ + public static void addProjectBuilderCommand(IProjectDescription description, ICommand command) throws CoreException { + ICommand[] oldCommands = description.getBuildSpec(); + ICommand oldBuilderCommand = getProjectBuilderCommand(description, command.getBuilderName()); + ICommand[] newCommands; + if (oldBuilderCommand == null) { + + // Add given builder to the end of the builder list + newCommands = new ICommand[oldCommands.length + 1]; + System.arraycopy(oldCommands, 0, newCommands, 0, oldCommands.length); + newCommands[oldCommands.length] = command; + } + else { + + // Replace old builder with given new one + for (int i = 0, max = oldCommands.length; i < max; i++) { + if (oldCommands[i] == oldBuilderCommand) { + oldCommands[i] = command; + break; + } + } + newCommands = oldCommands; + } + description.setBuildSpec(newCommands); + } + + /** + * Adds given nature as first nature to specified project. + */ + public static void addProjectNature(IProject project, String nature, IProgressMonitor monitor) throws CoreException { + if (project != null && nature != null) { + if (!project.hasNature(nature)) { + IProjectDescription desc = project.getDescription(); + String[] oldNatures = desc.getNatureIds(); + String[] newNatures = new String[oldNatures.length + 1]; + newNatures[0] = nature; + if (oldNatures.length > 0) { + System.arraycopy(oldNatures, 0, newNatures, 1, oldNatures.length); + } + desc.setNatureIds(newNatures); + project.setDescription(desc, monitor); + } + } + } + + /** + * Triggers a build of the given {@link IProject} instance, but only the + * Spring builder + * @param project the project to build + */ + public static void buildProject(IProject project) { + buildProject(project, SPRING_BUILDER_ID); + } + + /** + * Triggers a build of the given {@link IProject} instance with a full build + * and all builders + * @param project the project to build + */ + public static void buildFullProject(IProject project) { + buildProject(project, null); + } + + /** + * Triggers a build of the given {@link IProject} instance, but only the + * Spring builder + * @param project the project to build + * @param builderID the ID of the specific builder that should be executed + * on the project + * @since 3.2.0 + */ + public static void buildProject(IProject project, String builderID) { + if (ResourcesPlugin.getWorkspace().isAutoBuilding()) { + scheduleBuildInBackground(project, ResourcesPlugin.getWorkspace().getRuleFactory().buildRule(), + new Object[] { ResourcesPlugin.FAMILY_AUTO_BUILD }, builderID); + } + } + + /** + * Creates given folder and (if necessary) all of it's parents. + */ + public static void createFolder(IFolder folder, IProgressMonitor monitor) throws CoreException { + if (!folder.exists()) { + IContainer parent = folder.getParent(); + if (parent instanceof IFolder) { + createFolder((IFolder) parent, monitor); + } + folder.create(true, true, monitor); + } + } + + /** + * Creates specified simple project. + */ + public static IProject createProject(String projectName, IProjectDescription description, IProgressMonitor monitor) + throws CoreException { + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + IProject project = root.getProject(projectName); + if (!project.exists()) { + if (description == null) { + project.create(monitor); + } + else { + project.create(description, monitor); + } + } + else { + project.refreshLocal(IResource.DEPTH_INFINITE, monitor); + } + if (monitor.isCanceled()) { + throw new OperationCanceledException(); + } + if (!project.isOpen()) { + project.open(monitor); + } + return project; + } + + /** + * Finds the first line separator used by the given text. + * @return "\n" or "\r" or "\r\n", + * or null if none found + * @since 2.2.2 + */ + public static String findLineSeparator(char[] text) { + // find the first line separator + int length = text.length; + if (length > 0) { + char nextChar = text[0]; + for (int i = 0; i < length; i++) { + char currentChar = nextChar; + nextChar = i < length - 1 ? text[i + 1] : ' '; + switch (currentChar) { + case '\n': + return "\n"; //$NON-NLS-1$ + case '\r': + return nextChar == '\n' ? "\r\n" : "\r"; //$NON-NLS-1$ //$NON-NLS-2$ + } + } + } + // not found + return null; + } + + /** + * Returns the specified adapter for the given object or null + * if adapter is not supported. + */ + @SuppressWarnings("unchecked") + public static T getAdapter(Object object, Class adapter) { + if (object != null && adapter != null) { + if (adapter.isAssignableFrom(object.getClass())) { + return (T) object; + } + if (object instanceof IAdaptable) { + return (T) ((IAdaptable) object).getAdapter(adapter); + } + } + return null; + } + + public static IFile getDeploymentDescriptor(IProject project) { + if (SpringCoreUtils.hasProjectFacet(project, "jst.web")) { + IFile settingsFile = project.getFile(".settings/org.eclipse.wst.common.component"); + if (settingsFile.exists()) { + try { + NodeList nodes = (NodeList) EXPRESSION + .evaluate(parseDocument(settingsFile), XPathConstants.NODESET); + for (int i = 0; i < nodes.getLength(); i++) { + Element element = (Element) nodes.item(i); + if ("/".equals(element.getAttribute(DEPLOY_PATH))) { + String path = element.getAttribute(SOURCE_PATH); + if (path != null) { + IFile deploymentDescriptor = project.getFile(new Path(path).append("WEB-INF").append( + "web.xml")); + if (deploymentDescriptor.exists()) { + return deploymentDescriptor; + } + } + } + } + } + catch (Exception e) { + StatusHandler.log(new Status(IStatus.WARNING, CorePlugin.PLUGIN_ID, 1, e.getMessage(), e)); + } + } + } + return null; + } + + public static DocumentBuilder getDocumentBuilder() { + try { + DocumentBuilderFactory documentBuilderFactory = getDocumentBuilderFactory(); + documentBuilderFactory.setExpandEntityReferences(false); + return documentBuilderFactory.newDocumentBuilder(); + } + catch (Exception e) { + StatusHandler.log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "Error creating DocumentBuilder", e)); + } + return null; + } + + public static DocumentBuilderFactory getDocumentBuilderFactory() { + if (!DOCUMENT_BUILDER_ERROR) { + try { + // this might fail on IBM J9; therefore trying only once and + // then falling back to + // OSGi service reference as it should be + return DocumentBuilderFactory.newInstance(); + } + catch (Exception e) { + StatusHandler.log(new Status(IStatus.INFO, CorePlugin.PLUGIN_ID, + "Error creating DocumentBuilderFactory. Switching to OSGi service reference.")); + DOCUMENT_BUILDER_ERROR = true; + } + } + + BundleContext bundleContext = CorePlugin.getDefault().getBundle().getBundleContext(); + ServiceReference reference = bundleContext.getServiceReference(DocumentBuilderFactory.class.getName()); + if (reference != null) { + try { + synchronized (DOCUMENT_BUILDER_LOCK) { + return (DocumentBuilderFactory) bundleContext.getService(reference); + } + } + catch (Exception e) { + StatusHandler.log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, + "Error creating DocumentBuilderFactory", e)); + } + finally { + bundleContext.ungetService(reference); + } + } + + return null; + } + + /** + * Returns the line separator found in the given text. If it is null, or not + * found return the line delimiter for the given project. If the project is + * null, returns the line separator for the workspace. If still null, return + * the system line separator. + * @since 2.2.2 + */ + public static String getLineSeparator(String text, IProject project) { + String lineSeparator = null; + + // line delimiter in given text + if (text != null && text.length() != 0) { + lineSeparator = findLineSeparator(text.toCharArray()); + if (lineSeparator != null) { + return lineSeparator; + } + } + + // line delimiter in project preference + IScopeContext[] scopeContext; + if (project != null) { + scopeContext = new IScopeContext[] { new ProjectScope(project) }; + lineSeparator = Platform.getPreferencesService().getString(Platform.PI_RUNTIME, + Platform.PREF_LINE_SEPARATOR, null, scopeContext); + if (lineSeparator != null) { + return lineSeparator; + } + } + + // line delimiter in workspace preference + scopeContext = new IScopeContext[] { new InstanceScope() }; + lineSeparator = Platform.getPreferencesService().getString(Platform.PI_RUNTIME, Platform.PREF_LINE_SEPARATOR, + null, scopeContext); + if (lineSeparator != null) { + return lineSeparator; + } + + // system line delimiter + return LINE_SEPARATOR; + } + + /** + * Returns specified builder from given project description. + */ + public static ICommand getProjectBuilderCommand(IProjectDescription description, String builderID) + throws CoreException { + ICommand[] commands = description.getBuildSpec(); + for (int i = commands.length - 1; i >= 0; i--) { + if (commands[i].getBuilderName().equals(builderID)) { + return commands[i]; + } + } + return null; + } + + public static IPath getProjectLocation(IProject project) { + return (project.getRawLocation() != null ? project.getRawLocation() : project.getLocation()); + } + + public static URI getResourceURI(IResource resource) { + if (resource != null) { + URI uri = resource.getRawLocationURI(); + if (uri == null) { + uri = resource.getLocationURI(); + } + if (uri != null) { + String scheme = uri.getScheme(); + if (FILE_SCHEME.equalsIgnoreCase(scheme)) { + return uri; + } + else if (SOURCE_CONTROL_SCHEME.equals(scheme)) { + // special case of Rational Team Concert + IPath path = resource.getLocation(); + File file = path.toFile(); + if (file.exists()) { + return file.toURI(); + } + } + else { + IPathVariableManager variableManager = ResourcesPlugin.getWorkspace().getPathVariableManager(); + return variableManager.resolveURI(uri); + } + } + } + return null; + } + + public static SAXParser getSaxParser() { + if (!SAX_PARSER_ERROR) { + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + SAXParser parser = factory.newSAXParser(); + return parser; + } + catch (Exception e) { + StatusHandler.log(new Status(IStatus.INFO, CorePlugin.PLUGIN_ID, + "Error creating SaxParserFactory. Switching to OSGI service reference.")); + SAX_PARSER_ERROR = true; + } + } + + BundleContext bundleContext = CorePlugin.getDefault().getBundle().getBundleContext(); + ServiceReference reference = bundleContext.getServiceReference(SAXParserFactory.class.getName()); + if (reference != null) { + try { + synchronized (SAX_PARSER_LOCK) { + SAXParserFactory factory = (SAXParserFactory) bundleContext.getService(reference); + return factory.newSAXParser(); + } + } + catch (Exception e) { + StatusHandler + .log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, "Error creating SaxParserFactory", e)); + } + finally { + bundleContext.ungetService(reference); + } + } + + return null; + } + + /** + * Returns a list of all projects with the Spring project nature. + */ + public static Set getSpringProjects() { + Set projects = new LinkedHashSet(); + for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { + if (isSpringProject(project)) { + projects.add(project); + } + } + return projects; + } + + /** + * Returns true if given resource's project has the given nature. + */ + public static boolean hasNature(IResource resource, String natureId) { + if (resource != null && resource.isAccessible()) { + IProject project = resource.getProject(); + if (project != null) { + try { + return project.hasNature(natureId); + } + catch (CoreException e) { + StatusHandler.log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, + "An error occurred inspecting project nature", e)); + } + } + } + return false; + } + + /** + * Returns true if given text contains a placeholder, e.g. + * ${beansRef} . + */ + public static boolean hasPlaceHolder(String text) { + if (text == null || StringUtils.isBlank(text)) { + return false; + } + int pos = text.indexOf(PLACEHOLDER_PREFIX); + int elPos = text.indexOf(EL_PLACEHOLDER_PREFIX); + return ((pos != -1 || elPos != -1) && text.indexOf(PLACEHOLDER_SUFFIX, pos) != -1); + } + + public static boolean hasProjectFacet(IResource resource, String facetId) { + if (resource != null && resource.isAccessible()) { + try { + return JdtUtils.isJavaProject(resource) + && FacetedProjectFramework.hasProjectFacet(resource.getProject(), facetId); + } + catch (CoreException e) { + // TODO CD handle exception + } + } + return false; + } + + /** + * Returns true if Eclipse's runtime bundle has the same or a newer than + * given version. + */ + public static boolean isEclipseSameOrNewer(int majorVersion, int minorVersion) { + Bundle bundle = Platform.getBundle(Platform.PI_RUNTIME); + if (bundle != null) { + String versionString = (String) bundle.getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION); + try { + Version version = new Version(versionString); + int major = version.getMajor(); + if (major > majorVersion) { + return true; + } + if (major == majorVersion) { + int minor = version.getMinor(); + if (minor >= minorVersion) { + return true; + } + } + } + catch (IllegalArgumentException e) { + // ignore this exception as this can't occur in pratice + } + } + return false; + } + + /** + * Checks if the given {@link IResource} is a OSGi bundle manifest. + *

+ * Note: only the name and last segment of the folder name are checked. + * @since 2.0.5 + */ + public static boolean isManifest(IResource resource) { + // check if it is a MANIFEST.MF file in META-INF + if (resource != null + // && resource.isAccessible() + && resource.getType() == IResource.FILE && resource.getName().equals(BUNDLE_MANIFEST_FILE) + && resource.getParent() != null && resource.getParent().getProjectRelativePath() != null + && resource.getParent().getProjectRelativePath().lastSegment() != null + && resource.getParent().getProjectRelativePath().lastSegment().equals(BUNDLE_MANIFEST_FOLDER)) { + + // check if the manifest is not in an output folder + IPath filePath = resource.getFullPath(); + IJavaProject javaProject = JdtUtils.getJavaProject(resource); + if (javaProject != null) { + try { + IPath defaultOutputLocation = javaProject.getOutputLocation(); + if (defaultOutputLocation != null && defaultOutputLocation.isPrefixOf(filePath)) { + return false; + } + for (IClasspathEntry entry : javaProject.getRawClasspath()) { + if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { + IPath outputLocation = entry.getOutputLocation(); + if (outputLocation != null && outputLocation.isPrefixOf(filePath)) { + return false; + } + } + } + } + catch (JavaModelException e) { + // don't care here + } + return true; + } + else { + // if the project is not a java project -> it is the manifest + return true; + } + } + return false; + } + + /** + * Returns true if given resource's project is a Spring project. + */ + public static boolean isSpringProject(IResource resource) { + return hasNature(resource, NATURE_ID); + } + + /** + * Returns true if Eclipse's runtime bundle has the same or a newer than + * given version. + */ + public static boolean isVersionSameOrNewer(String versionString, int majorVersion, int minorVersion, + int microVersion) { + return new Version(versionString).compareTo(new Version(majorVersion, minorVersion, microVersion)) >= 0; + } + + public static Document parseDocument(IFile deploymentDescriptor) { + try { + if (getResourceURI(deploymentDescriptor) != null) { + return parseDocument(getResourceURI(deploymentDescriptor)); + } + return getDocumentBuilder().parse(new InputSource(deploymentDescriptor.getContents())); + } + catch (SAXException e) { + throw new RuntimeException(e); + } + catch (IOException e) { + throw new RuntimeException(e); + } + catch (CoreException e) { + throw new RuntimeException(e); + } + } + + public static Document parseDocument(URI deploymentDescriptor) { + try { + return getDocumentBuilder().parse(deploymentDescriptor.toString()); + } + catch (SAXException e) { + throw new RuntimeException(e); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Removes given builder from specified project. + */ + public static void removeProjectBuilder(IProject project, String builderID, IProgressMonitor monitor) + throws CoreException { + if (project != null && builderID != null) { + IProjectDescription desc = project.getDescription(); + ICommand[] commands = desc.getBuildSpec(); + for (int i = commands.length - 1; i >= 0; i--) { + if (commands[i].getBuilderName().equals(builderID)) { + ICommand[] newCommands = new ICommand[commands.length - 1]; + System.arraycopy(commands, 0, newCommands, 0, i); + System.arraycopy(commands, i + 1, newCommands, i, commands.length - i - 1); + // Commit the spec change into the project + desc.setBuildSpec(newCommands); + project.setDescription(desc, monitor); + break; + } + } + } + } + + /** + * Removes given nature from specified project. + */ + public static void removeProjectNature(IProject project, String nature, IProgressMonitor monitor) + throws CoreException { + if (project != null && nature != null) { + if (project.exists() && project.hasNature(nature)) { + + // first remove all problem markers (including the + // inherited ones) from Spring beans project + if (nature.equals(NATURE_ID)) { + project.deleteMarkers(MARKER_ID, true, IResource.DEPTH_INFINITE); + } + + // now remove project nature + IProjectDescription desc = project.getDescription(); + String[] oldNatures = desc.getNatureIds(); + String[] newNatures = new String[oldNatures.length - 1]; + int newIndex = oldNatures.length - 2; + for (int i = oldNatures.length - 1; i >= 0; i--) { + if (!oldNatures[i].equals(nature)) { + newNatures[newIndex--] = oldNatures[i]; + } + } + desc.setNatureIds(newNatures); + project.setDescription(desc, monitor); + } + } + } + + /** + * Verify that file can safely be modified; eventually checkout the file + * from source code control. + * @return true if resource can be modified + * @since 2.2.9 + */ + public static boolean validateEdit(IFile... files) { + for (IFile file : files) { + if (!file.exists()) { + return false; + } + } + IStatus status = ResourcesPlugin.getWorkspace().validateEdit(files, IWorkspace.VALIDATE_PROMPT); + if (status.isOK()) { + return true; + } + return false; + } + + private static void scheduleBuildInBackground(final IProject project, ISchedulingRule rule, + final Object[] jobFamilies, final String builderID) { + Job job = new Job("Building workspace") { + + @Override + public boolean belongsTo(Object family) { + if (jobFamilies == null || family == null) { + return false; + } + for (Object jobFamilie : jobFamilies) { + if (family.equals(jobFamilie)) { + return true; + } + } + return false; + } + + @Override + public IStatus run(IProgressMonitor monitor) { + try { + if (builderID != null) { + project.build(IncrementalProjectBuilder.FULL_BUILD, builderID, null, monitor); + } + else { + project.build(IncrementalProjectBuilder.FULL_BUILD, monitor); + } + return Status.OK_STATUS; + } + catch (CoreException e) { + return new Status(Status.ERROR, CorePlugin.PLUGIN_ID, 1, "Error during build of project [" + + project.getName() + "]", e); + } + } + }; + if (rule != null) { + job.setRule(rule); + } + job.setPriority(Job.BUILD); + job.schedule(); + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/StatusHandler.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/StatusHandler.java new file mode 100644 index 000000000..c914efcdc --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/StatusHandler.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.IStatus; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; + + +/** + * @author Steffen Pingel + * @author Leo Dos Santos + * @author Christian Dupuis + */ +public class StatusHandler { + + public static void log(IStatus status) { + CorePlugin plugin = CorePlugin.getDefault(); + if (plugin!=null) { + ILog log = plugin.getLog(); + if (log!=null) { + log.log(status); + } + } + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/ZipFileUtil.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/ZipFileUtil.java new file mode 100644 index 000000000..bbda98c4a --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/ZipFileUtil.java @@ -0,0 +1,191 @@ +/******************************************************************************* + * Copyright (c) 2012, 2018 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Path; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipInputStream; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.springsource.ide.eclipse.commons.core.process.StandardProcessRunner; +import org.springsource.ide.eclipse.commons.core.util.OsUtils; + + +/** + * @author Steffen Pingel + * @author Leo Dos Santos + * @author Christian Dupuis + * @author Kris De Volder + * @since 2.0 + */ +public class ZipFileUtil { + + /** + * This Zip utility doesn't know how to extract file permission correctly + * from zip files (does Java ZipEntry even provide this info? It seems + * not!). + *

+ * To remedy this problem, clients can provide their own logic for setting + * permissions they may care about. A subclass of PermissionSetter is used + * to determine the permission setting logic. + * + * @author Kris De Volder + * + * @since 2.8 + */ + public static abstract class PermissionSetter { + + /** + * Called after a file was succesfully extracted from the zip archive. + * @throws IOException + */ + public abstract void fileUnzipped(ZipEntry entry, File entryFile) throws IOException; + + /** + * A permission setter that does nothing. + */ + public static final PermissionSetter NULL = new PermissionSetter() { + @Override + public void fileUnzipped(ZipEntry entry, File entryFile) { + // Do nothing + } + }; + + /** + * Creates an executable permission setter based on a list of file + * extensions. + *

+ * Any file ending with the extension will be made executable. + */ + public static PermissionSetter executableExtensions(final String... exts) { + if (OsUtils.isWindows()) { + // It may be ok to do nothing for windows? (note: not tested!) + return NULL; + } + else { + // Assume we are unix if not Windows (Mac OS X is ok) + return new PermissionSetter() { + @Override + public void fileUnzipped(ZipEntry entry, File entryFile) throws IOException { + for (String ext : exts) { + if (entryFile.getName().endsWith(ext)) { + // This only works with Java 6: + // entryFile.setExecutable(true); + + // This only works on Unix: + StandardProcessRunner runner = new StandardProcessRunner(); + try { + runner.run(new File("."), "chmod", "a+x", entryFile.toString()); + } + catch (InterruptedException e) { + // Restore the interrupted status (see + // https://www.ibm.com/developerworks/java/library/j-jtp05236/index.html) + Thread.currentThread().interrupt(); + } + return; // No sense making a file executable + // more than once. + } + } + } + }; + } + } + + } + + private static final int BUFFER_SIZE = 512 * 1024; + + public static void unzip(URL source, File targetFile, IProgressMonitor monitor) throws IOException { + unzip(source, targetFile, null, monitor); + } + + public static void unzip(URL source, File targetFile, String prefix, IProgressMonitor monitor) throws IOException { + unzip(source, targetFile, prefix, PermissionSetter.NULL, monitor); + } + + public static void unzip(URL source, File targetFile, String prefix, PermissionSetter permsetter, + IProgressMonitor monitor) throws IOException { + if (monitor == null) { + monitor = new NullProgressMonitor(); + } + try { + monitor.beginTask("Extracting " + source.getFile(), IProgressMonitor.UNKNOWN); + + byte[] buffer = new byte[BUFFER_SIZE]; + ZipInputStream zipIn = new ZipInputStream(source.openStream()); + Path normalizedTargetFilePath = targetFile.toPath().normalize(); + try { + ZipEntry entry; + while ((entry = zipIn.getNextEntry()) != null) { + String name = entry.getName(); + if (prefix != null && name.startsWith(prefix)) { + name = name.substring(prefix.length()); + if (name.length() > 1) { + // cut off separator + name = name.substring(1); + } + } + + Policy.checkCancelled(monitor); + monitor.subTask(name); + File entryFile = new File(targetFile, name); + /* + * Ensure the outputdir + name doesn't leave the outputdir. + */ + if (!entryFile.toPath().normalize().startsWith(normalizedTargetFilePath)) { + throw new ZipException("The file " + name + + " is trying to leave the target output directory of " + targetFile); + } + if (entry.isDirectory()) { + entryFile.mkdirs(); + } + else { + entryFile.getParentFile().mkdirs(); + FileOutputStream out = new FileOutputStream(entryFile); + try { + int len; + while ((len = zipIn.read(buffer)) >= 0) { + Policy.checkCancelled(monitor); + out.write(buffer, 0, len); + } + } + finally { + out.close(); + } + long modTime = entry.getTime(); + if (modTime > 0) { + entryFile.setLastModified(modTime); + } + permsetter.fileUnzipped(entry, entryFile); + } + } + } + finally { + zipIn.close(); + } + } + finally { + monitor.done(); + } + } + + public static void unzip(File zipFile, File unzipDir, IProgressMonitor monitor) throws MalformedURLException, IOException { + unzip(zipFile.toURI().toURL(), unzipDir, monitor); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/preferences/GlobalPreferenceEnhancer.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/preferences/GlobalPreferenceEnhancer.java new file mode 100644 index 000000000..21430b6e4 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/preferences/GlobalPreferenceEnhancer.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.preferences; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.osgi.service.prefs.BackingStoreException; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; + +public class GlobalPreferenceEnhancer { + + // JDT ui constants + private static final String JDT_UI_BUNDLE_ID = "org.eclipse.jdt.ui"; + + // org.eclipse.jdt.ui.PreferenceConstants.CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVA + private static final String CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVA = "content_assist_autoactivation_triggers_java"; + + // org.eclipse.jdt.ui.PreferenceConstants.CODEASSIST_AUTOACTIVATION_DELAY + private static final String CODEASSIST_AUTOACTIVATION_DELAY = "content_assist_autoactivation_delay"; + + // org.eclipse.jdt.ui.PreferenceConstants.EDITOR_ESCAPE_STRINGS + private static final String EDITOR_ESCAPE_STRINGS = "escape¥Strings"; + + // org.eclipse.jdt.ui.PreferenceConstants.EDITOR_SMART_PASTE + private static final String EDITOR_SMART_PASTE = "smartPaste"; + + // org.eclipse.jdt.ui.PreferenceConstants.EDITOR_SMART_TAB + private static final String EDITOR_SMART_TAB = "smart_tab"; + + // org.eclipse.jdt.ui.PreferenceConstants.EDITOR_SMART_BACKSPACE + private static final String EDITOR_SMART_BACKSPACE = "smart_backspace"; + + // org.eclipse.jdt.ui.PreferenceConstants.EDITOR_SMART_OPENING_BRACE + private static final String EDITOR_SMART_OPENING_BRACE = "smart_opening_brace"; + + // org.eclipse.jdt.ui.PreferenceConstants.EDITOR_SMART_SEMICOLON + private static final String EDITOR_SMART_SEMICOLON = "smart_semicolon"; + + // M2E constants + private static final String M2E_CORE_BUNDLE_ID = "org.eclipse.m2e.core"; + + // org.eclipse.m2e.core.internal.preferences.MavenPreferenceConstants.P_HIDE_FOLDERS_OF_NESTED_PROJECTS + private static final String P_HIDE_FOLDERS_OF_NESTED_PROJECTS = "eclipse.m2.hideFoldersOfNestedProjects"; + + // org.eclipse.m2e.core.internal.preferences.MavenPreferenceConstants.P_DEFAULT_POM_EDITOR_PAGE + private static final String P_DEFAULT_POM_EDITOR_PAGE = "eclipse.m2.defaultPomEditorPage"; + + // org.eclipse.m2e.core.internal.preferences.MavenPreferenceConstants.P_UPDATE_INDEXES + private static final String P_UPDATE_INDEXES = "eclipse.m2.updateIndexes"; + + // jdt core constatnts + private static final String JDT_CORE_BUNDLE_ID = "org.eclipse.jdt.core"; + + // org.eclipse.jdt.core.JavaCore.COMPILER_PB_NON_NLS_STRING_LITERAL + private static final String COMPILER_PB_NON_NLS_STRING_LITERAL = "org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral"; + + public void enhanceAllPreferences() { + enhanceM2EPreferences(); + enhanceJDTPreferences(); + } + + public void enhanceM2EPreferences() { + IEclipsePreferences m2eCorePrefs = InstanceScope.INSTANCE.getNode(M2E_CORE_BUNDLE_ID); + if (m2eCorePrefs != null) { + m2eCorePrefs.putBoolean(P_HIDE_FOLDERS_OF_NESTED_PROJECTS, true); + m2eCorePrefs.putBoolean(P_DEFAULT_POM_EDITOR_PAGE, true); + m2eCorePrefs.putBoolean(P_UPDATE_INDEXES, false); + try { + m2eCorePrefs.flush(); + } + catch (BackingStoreException e) { + handleException(e); + } + } + } + + public void enhanceJDTPreferences() { + IEclipsePreferences jdtCorePrefs = InstanceScope.INSTANCE.getNode(JDT_CORE_BUNDLE_ID); + if (jdtCorePrefs != null) { + jdtCorePrefs.put(COMPILER_PB_NON_NLS_STRING_LITERAL, "ignore"); + + try { + jdtCorePrefs.flush(); + } + catch (BackingStoreException e) { + handleException(e); + } + } + + IEclipsePreferences jdtUiPrefs = InstanceScope.INSTANCE.getNode(JDT_UI_BUNDLE_ID); + if (jdtUiPrefs != null) { + jdtUiPrefs.putBoolean(EDITOR_SMART_PASTE, true); + jdtUiPrefs.putBoolean(EDITOR_ESCAPE_STRINGS, true); + jdtUiPrefs.putInt(CODEASSIST_AUTOACTIVATION_DELAY, 40); + //jdtUiPrefs.put(CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVA, "_.abcdefghijklmnopqrstuvwxyz"); + jdtUiPrefs.putBoolean(EDITOR_SMART_SEMICOLON, true); + jdtUiPrefs.putBoolean(EDITOR_SMART_OPENING_BRACE, true); + jdtUiPrefs.putBoolean(EDITOR_SMART_BACKSPACE, true); + jdtUiPrefs.putBoolean(EDITOR_SMART_TAB, true); + + // org.eclipse.jdt.ui.PreferenceConstants.CODEASSIST_FAVORITE_STATIC_MEMBERS + // disable for now until we figure out the best things to include + // here; + // it is a semi-colon separateted list where each element looks like + // a static import statement + // jdtUiPrefs.put("content_assist_favorite_static_members", + // "org.eclipse.jdt.internal.ui.javaeditor.IJavaEditorActionConstants.*"); + + try { + jdtUiPrefs.flush(); + } + catch (BackingStoreException e) { + handleException(e); + } + } + } + + public void undoAll() { + undoJDTEnhance(); + undoM2EEnhance(); + } + + public void undoJDTEnhance() { + IEclipsePreferences jdtCorePrefs = InstanceScope.INSTANCE.getNode(JDT_CORE_BUNDLE_ID); + if (jdtCorePrefs != null) { + jdtCorePrefs.remove(COMPILER_PB_NON_NLS_STRING_LITERAL); + + try { + jdtCorePrefs.flush(); + } + catch (BackingStoreException e) { + handleException(e); + } + } + + IEclipsePreferences jdtUiPrefs = InstanceScope.INSTANCE.getNode(JDT_UI_BUNDLE_ID); + if (jdtUiPrefs != null) { + jdtUiPrefs.remove(EDITOR_SMART_PASTE); + jdtUiPrefs.remove(EDITOR_ESCAPE_STRINGS); + jdtUiPrefs.remove(CODEASSIST_AUTOACTIVATION_DELAY); + jdtUiPrefs.remove(CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVA); + jdtUiPrefs.remove(EDITOR_SMART_SEMICOLON); + jdtUiPrefs.remove(EDITOR_SMART_OPENING_BRACE); + jdtUiPrefs.remove(EDITOR_SMART_BACKSPACE); + jdtUiPrefs.remove(EDITOR_SMART_TAB); + + try { + jdtUiPrefs.flush(); + } + catch (BackingStoreException e) { + handleException(e); + } + } + } + + public void undoM2EEnhance() { + IEclipsePreferences m2eCorePrefs = InstanceScope.INSTANCE.getNode(M2E_CORE_BUNDLE_ID); + if (m2eCorePrefs != null) { + m2eCorePrefs.remove(P_HIDE_FOLDERS_OF_NESTED_PROJECTS); + m2eCorePrefs.remove(P_DEFAULT_POM_EDITOR_PAGE); + m2eCorePrefs.remove(P_UPDATE_INDEXES); + try { + m2eCorePrefs.flush(); + } + catch (BackingStoreException e) { + handleException(e); + } + } + } + + private void handleException(BackingStoreException e) { + CorePlugin.getDefault().getLog() + .log(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, e.getLocalizedMessage(), e)); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/preferences/StsProperties.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/preferences/StsProperties.java new file mode 100644 index 000000000..1033f9ea9 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/preferences/StsProperties.java @@ -0,0 +1,181 @@ +/******************************************************************************* + * Copyright (c) 2012, 2020 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.preferences; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Properties; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; + +/** + * An instance of this class provides a mechanism to retrieve String properties. + * This is similar to a Java system properties. However it has support for reading these + * properties from a fixed url. This allows us to change the properties after release. + *

+ * Properties in this class can come from several different sources, listed here in + * decreasing order of priority: + * + * 1) Java System properties (set via -Dmy.prop.name=value) in STS.ini + * (properties set this way override anything else). + * 2) default values hard-coded in this class. + * (used only if property was not set via either 1 or 2). + * + * @since 3.4.M1 + * @since 3.8.4 : no more reading props from url. Only hard-coded or system props used (too many + * issues with the network accesses blocking UI thread.) + * + * @author Kris De Volder + */ +public class StsProperties { + + + //Note: there is also a class called 'ResourceProvider'.. which reads various properties + // from eclipse extension points. This is different because the STSProperties themselves + // are read from an external url. + //The ResourceProvider only allows properties to defined by extensions contained in plugins + // installed into the Ecliple platform. + + /** + * This class is a singleton. This holds the instance once created. + */ + private static StsProperties instance = null; + + public static StsProperties getInstance(IProgressMonitor mon) { + if (instance==null) { + StsProperties newInstance = new StsProperties(mon); + instance = newInstance; + } + return instance; + } + + private final Properties props; + + private StsProperties(IProgressMonitor mon) { + mon.beginTask("Read Sts Properties", 11); + try { + props = createProperties(); + } finally { + mon.done(); + } + } + + protected Properties createProperties() { + Properties props = new Properties(); + + // Default properties (guarantees certain properties have a value no + // matter what). + + props.put("spring.site.url", "https://spring.io"); + props.put("spring.initializr.json.url", "https://start.spring.io"); + + //Urls used in the dashboard. For each XXX.url=... property, if + // - XXX.url.label is defined that label will be used for the corresponding + // dashboard tab instead of the html page title (title tends to be too long). + // - XXX.url.external is defined that url will always be openened in an external browser. + + //Docs + props.put("spring.docs.url", "https://spring.io/docs"); + props.put("spring.docs.url.label", "Spring Docs"); + props.put("spring.docs.url.external", "false"); + + //Blog + props.put("spring.blog.url", "https://spring.io/blog"); + props.put("spring.blog.url.label", "Blog"); + props.put("spring.blog.url.external", "false"); + + //Tracker: + props.put("sts.tracker.url", "https://github.com/spring-projects/spring-ide/issues"); + props.put("sts.tracker.url.label", "Issues"); + props.put("sts.tracker.url.external", "true"); + + //New and Noteworthy + props.put("sts.nan.url", "https://static.springsource.org/sts/nan/latest/NewAndNoteworthy.html"); + props.put("sts.nan.url.label", "New and Noteworthy"); + props.put("sts.nan.url.external", "false"); + + //Forum: + props.put("sts.forum.url", "https://forum.springsource.org/forumdisplay.php?32-SpringSource-Tool-Suite"); + props.put("sts.forum.url.label", "Forum"); + props.put("sts.forum.url.external", "false"); + + //Guides + props.put("spring.guides.url", "https://spring.io/guides"); + props.put("spring.guides.url.label", "Guides"); + props.put("spring.guides.url.external", "true"); + + //Spring boot runtime + props.put("spring.boot.install.url", "https://repo.spring.io/release/org/springframework/boot/spring-boot-cli/2.1.15.RELEASE/spring-boot-cli-2.1.15.RELEASE-bin.zip"); + //Discovery url for spring reference app + props.put("spring.reference.app.discovery.url", "https://raw.github.com/kdvolder/spring-reference-apps-meta/master/reference-apps.json"); + + //Url for webservice that generates typegraph for spring boot jar type content assist + props.put("spring.boot.typegraph.url", "https://aetherial.cfapps.io/boot/typegraph"); + + //Default version of spring boot, assumed when we need a version but can't determine it from the classpath of the project. + //Typically this should point to the latest version of spring-boot (the one used by spring-initialzr app). + props.put("spring.boot.default.version", "2.1.15.RELEASE"); + props.put("spring.boot.cloud.default.version", "2.1.0.RELEASE"); + + return props; + } + + /** + * Procudes names of properties that have explicitly been set, either from properties file + * or by the explicitly provided defaults. More precisely this does not return + * properties simply inherited from Java system properties. + */ + public Collection getExplicitProperties() { + ArrayList keys = new ArrayList(); + for (Object string : props.keySet()) { + if (string instanceof String) { + keys.add((String) string); + } + } + return keys; + } + + public String get(String key) { + String value = System.getProperty(key); + if (value == null) { + value = props.getProperty(key); + } + return value; + } + + public boolean get(String key, boolean deflt) { + String value = get(key); + if (value!=null) { + return Boolean.valueOf(value); + } + return deflt; + } + + public static StsProperties getInstance() { + return getInstance(new NullProgressMonitor()); + } + + public URL url(String prop) { + String s = get(prop); + try { + if (s!=null) { + return new URL(get(prop)); + } + } catch (Exception e) { + CorePlugin.log(e); + } + return null; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/OutputWriter.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/OutputWriter.java new file mode 100644 index 000000000..e46892c7c --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/OutputWriter.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.process; + +/** + * @author Christian Dupuis + * @author Steffen Pingel + * @since 2.5.2 + */ +public interface OutputWriter { + + void write(String line); + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/ProcessRunner.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/ProcessRunner.java new file mode 100644 index 000000000..d84216b59 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/ProcessRunner.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.process; + +import java.io.File; +import java.io.IOException; + +/** + * @author Christian Dupuis + * @author Steffen Pingel + * @since 2.5.2 + */ +public interface ProcessRunner { + + int run(File workingDir, String... command) throws IOException, InterruptedException; + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/StandardProcessRunner.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/StandardProcessRunner.java new file mode 100644 index 000000000..f2c7afe58 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/StandardProcessRunner.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.process; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.concurrent.CountDownLatch; + +/** + * @author Christian Dupuis + * @author Steffen Pingel + * @since 2.5.2 + */ +public final class StandardProcessRunner implements ProcessRunner { + + private static final String[] ENVIRONMENT = new String[] { + String.format("JAVA_HOME=%s", System.getProperty("java.home")), + String.format("PATH=%s", System.getenv("PATH")), + String.format("USERDOMAIN=%s", System.getenv("USERDOMAIN")), + String.format("USERNAME=%s", System.getenv("USERNAME")), + String.format("USERPROFILE=%s", System.getenv("USERPROFILE")) }; + + private final OutputWriter[] standardOutputWriters; + + private final OutputWriter[] standardErrorWriters; + + public StandardProcessRunner() { + this(new OutputWriter[0], new OutputWriter[0]); + } + + public StandardProcessRunner(OutputWriter standardOutputWriter, OutputWriter standardErrorWriter) { + this(new OutputWriter[] { standardOutputWriter }, new OutputWriter[] { standardErrorWriter }); + } + + public StandardProcessRunner(OutputWriter[] standardOutputWriters, OutputWriter[] standardErrorWriters) { + this.standardOutputWriters = standardOutputWriters; + this.standardErrorWriters = standardErrorWriters; + } + + public int run(File workingDir, String... command) throws IOException, InterruptedException { + Process process; + + if (command.length == 1) { + process = Runtime.getRuntime().exec(command[0], ENVIRONMENT, workingDir); + } + else { + process = Runtime.getRuntime().exec(command, ENVIRONMENT, workingDir); + } + + CountDownLatch latch = new CountDownLatch(2); + new Thread(new StreamReader(process.getInputStream(), this.standardOutputWriters, latch)).start(); + new Thread(new StreamReader(process.getErrorStream(), this.standardErrorWriters, latch)).start(); + + int returnCode = process.waitFor(); + latch.await(); + + return returnCode; + } + + private static final class StreamReader implements Runnable { + + private final BufferedReader input; + + private final OutputWriter[] outputs; + + private final CountDownLatch latch; + + public StreamReader(InputStream input, OutputWriter[] outputs, CountDownLatch latch) { + this.input = new BufferedReader(new InputStreamReader(input)); + this.outputs = outputs; + this.latch = latch; + } + + public void run() { + try { + String line = null; + while ((line = this.input.readLine()) != null) { + for (OutputWriter output : this.outputs) { + output.write(line); + } + } + } + catch (IOException e) { + } + finally { + try { + if (this.input != null) { + this.input.close(); + } + } + catch (IOException e) { + } + finally { + this.latch.countDown(); + } + } + } + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/SystemErrOutputWriter.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/SystemErrOutputWriter.java new file mode 100644 index 000000000..6ca9a03a3 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/SystemErrOutputWriter.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.process; + +/** + * @author Christian Dupuis + * @author Steffen Pingel + * @since 2.5.2 + */ +public class SystemErrOutputWriter implements OutputWriter { + + public void write(String line) { + System.err.println(line); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/SystemOutOutputWriter.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/SystemOutOutputWriter.java new file mode 100644 index 000000000..231f43adc --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/process/SystemOutOutputWriter.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.process; + +/** + * @author Christian Dupuis + * @author Steffen Pingel + * @since 2.5.2 + */ +public final class SystemOutOutputWriter implements OutputWriter { + + public void write(String line) { + System.out.println(line); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/IPropertyStore.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/IPropertyStore.java new file mode 100644 index 000000000..3ab3eb5d2 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/IPropertyStore.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2015 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.pstore; + +/** + * An instance of this provides a way to store and retrieve + * properties. The properties are persisted somehow. + *

+ * This interface is deliberately kept very sparse so as to be + * 'easy' to implement. Its an interface meant for implementors. + *

+ * If you are a client you can an wrap instance of this interface + * into a {@link PropertyStoreApi} for a more convenient api + * with more operations. + * + * @author Kris De Volder + */ +public interface IPropertyStore { + String get(String key); + void put(String key, String value) throws Exception; +} \ No newline at end of file diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/IScopedPropertyStore.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/IScopedPropertyStore.java new file mode 100644 index 000000000..d223bef9e --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/IScopedPropertyStore.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2015 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.pstore; + +/** + * An instance of this provides a way to store and retrieve + * properties associated with some 'scope' object of type T. + *

+ * The properties are persisted somehow. + */ +public interface IScopedPropertyStore { + String get(T scope, String key); + void put(T scope, String key, String value) throws Exception; +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/InMemoryPropertyStore.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/InMemoryPropertyStore.java new file mode 100644 index 000000000..775bbab6f --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/InMemoryPropertyStore.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2015 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.pstore; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author Kris De Volder + */ +public class InMemoryPropertyStore implements IPropertyStore { + + private Map props = new HashMap<>(); + + @Override + public synchronized String get(String key) { + return props.get(key); + } + + @Override + public synchronized void put(String key, String value) throws Exception { + props.put(key, value); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/PreferenceBasedStore.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/PreferenceBasedStore.java new file mode 100644 index 000000000..0e3e696b5 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/PreferenceBasedStore.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2015 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.pstore; + +import org.eclipse.core.runtime.preferences.IEclipsePreferences; + +/** + * An abstract base class to create {@link IScopedPropertyStore} that uses + * {@link IEclipsePreferences} to persist properties. + */ +public abstract class PreferenceBasedStore implements IScopedPropertyStore { + + protected abstract IEclipsePreferences createPrefs(T element); + + @Override + public String get(T p, String k) { + IEclipsePreferences prefs = createPrefs(p); + return prefs.get(k, null); + } + + @Override + public void put(T element, String key, String value) throws Exception { + IEclipsePreferences prefs = createPrefs(element); + if (value==null) { + prefs.remove(key); + } else { + prefs.put(key, value); + } + prefs.flush(); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/PropertyFileStore.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/PropertyFileStore.java new file mode 100644 index 000000000..b39747768 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/PropertyFileStore.java @@ -0,0 +1,194 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.pstore; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.AclEntry; +import java.nio.file.attribute.AclEntryPermission; +import java.nio.file.attribute.AclEntryType; +import java.nio.file.attribute.AclFileAttributeView; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.UserPrincipal; +import java.nio.file.attribute.UserPrincipalLookupService; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Properties; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.springsource.ide.eclipse.commons.core.util.OsUtils; +import org.springsource.ide.eclipse.commons.livexp.util.Log; + +/** + * Implementation of IPropertyStore backed by a .properties file. + * The properties file is 'private'. I.e. it is created such that only + * the onwer of the file is allowed access to it. + */ +public class PropertyFileStore implements IPropertyStore { + + final private File file; + final private Properties props = new Properties(); + + Job flushProperties = new Job("Save private properties") { + + boolean errorLogged = false; + + { + setSystem(true); + } + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + createFileIfNeeded(file); + try (OutputStream out = new FileOutputStream(file)) { + props.store(out, "Private properties"); + } + } catch (IOException e) { + if (!errorLogged) { + errorLogged = true; //complaining once about failure to store props is enough! + Log.log(e); + } + } + return Status.OK_STATUS; + } + }; + + protected void createFileIfNeeded(File f) throws IOException { + if (!f.exists()) { + createFile(f); + setPermissions(f); + } + } + + protected void setPermissions(File f) throws IOException { + if (OsUtils.isWindows()) { + setWindowsPerissions(f); + } else { + setUnixPermissions(f); + } + } + + protected void setWindowsPerissions(File f) throws IOException { + Path file = f.toPath(); + AclFileAttributeView aclAttr = Files.getFileAttributeView(file, AclFileAttributeView.class); + + UserPrincipalLookupService upls = file.getFileSystem().getUserPrincipalLookupService(); + UserPrincipal user = upls.lookupPrincipalByName(System.getProperty("user.name")); + AclEntry.Builder builder = AclEntry.newBuilder(); + builder.setPermissions( EnumSet.allOf(AclEntryPermission.class)); +// This was here before but it seems not enough to actually allow reading the file: +// builder.setPermissions( EnumSet.of( +// AclEntryPermission.READ_DATA, +// AclEntryPermission.WRITE_DATA, +// AclEntryPermission.APPEND_DATA, +// AclEntryPermission.READ_ACL, +// AclEntryPermission.WRITE_ACL, +// AclEntryPermission.READ_ATTRIBUTES, +// AclEntryPermission.WRITE_ATTRIBUTES, +// AclEntryPermission.READ_NAMED_ATTRS, +// AclEntryPermission.WRITE_NAMED_ATTRS, +// AclEntryPermission.DELETE +// )); + builder.setPrincipal(user); + builder.setType(AclEntryType.ALLOW); + aclAttr.setAcl(Collections.singletonList(builder.build())); + } + + protected void setUnixPermissions(File f) throws IOException { + Files.setPosixFilePermissions(file.toPath(), EnumSet.of( + PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE) + ); + } + + protected void createFile(File f) throws IOException { + if (f.getParentFile() != null) { + f.getParentFile().mkdirs(); + } + f.createNewFile(); + } + + public PropertyFileStore(File file) { + this.file = file; + try { + if (file.exists()) { + try (InputStream in = new FileInputStream(file)) { + props.load(in); + } + } + } catch (IOException e) { + //Log and move on. This is not 'critical'. Maybe the file is corrupt and will be overwritten + // or something else is wrong and we won't be able to persist props. But at least + //props will be working in memory. + Log.log(e); + } + } + + @Override + public synchronized String get(String key) { + return props.getProperty(key); + } + + @Override + public void put(String key, String value) throws Exception { + Object oldValue = nullPut(key, value); + if (!Objects.equals(oldValue, value)) { + flushProperties.schedule(); + } + } + + private Object nullPut(String key, String value) { + if (value==null) { + return props.remove(key); + } else { + return props.put(key, value); + } + } + + public boolean isEmpty() { + return props.isEmpty(); + } + + /** + * Blocks the current thread until all dirty data has been written to disk. This meant for testing purposes. + * E.g. a test that wants to check contents of the backing file after performing ops on the store. + */ + public void sync() throws InterruptedException { + flushProperties.join(); + } + + /** + * Convert the contents currently in the store into a Map. Mainly meant for testing purposes to easily compare + * the map contents to expected contents. Not thread safe. Test code is expected to call this at a time + * when the work on the map is 'done'. + */ + public Map asMap() { + Map builder = new HashMap<>(); + for (Entry e : props.entrySet()) { + builder.put((String)e.getKey(), (String)e.getValue()); + } + return Collections.unmodifiableMap(builder); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/PropertyStoreApi.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/PropertyStoreApi.java new file mode 100644 index 000000000..3770fc97e --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/PropertyStoreApi.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2015, 2016 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.pstore; + +import org.springsource.ide.eclipse.commons.core.util.StringUtil; +import org.springsource.ide.eclipse.commons.frameworks.core.util.ArrayEncoder; +import org.springsource.ide.eclipse.commons.livexp.util.Log; + +/** + * This is a 'wrapper' for a basic 'IPropertyStore' instance that is used as + * the 'backing store' to persist metadata. + *

+ * The 'PropertyStoreApi' implements additional convenience methods that encode + * a variety of data types for storing into the backing store. + *

+ * The idea is that leaves us free to add lots of convenience methods here + * without making the basic IPropertyStore interface bigger and harder to implement. + *

+ * In other words the responsibility of 'PropertyStoreApi' is 'encoding' and + * 'decoding' data into a storeable format. But it is not concerned with + * how/where this encoded data is persisted. + *

+ * IPropertyStore's, on the other hand is concerned with how / where the data + * is stored but doesn't deal with encoding data (beyond basic key-value + * pairs). + * + * @author Kris De Volder + */ +public class PropertyStoreApi { + + private final IPropertyStore backingStore; + + public PropertyStoreApi(IPropertyStore backingStore) { + this.backingStore = backingStore; + } + + public String get(String key) { + return backingStore.get(key); + } + + public void put(String key, String value) throws Exception { + backingStore.put(key, value); + } + + public void put(String key, String[] values) throws Exception { + if (values==null) { + backingStore.put(key, null); + } else { + backingStore.put(key, ArrayEncoder.encode(values)); + } + } + + public String[] get(String key, String[] dflt) throws Exception { + String encoded = backingStore.get(key); + if (encoded==null) { + return dflt; + } else { + return ArrayEncoder.decode(encoded); + } + } + + public boolean get(String key, boolean dflt) { + String string = get(key); + if (StringUtil.hasText(string)) { + try { + return Boolean.parseBoolean(string); + } catch (Exception e) { + Log.log(e); + } + } + return dflt; + } + + public int get(String key, int dflt) { + String string = get(key); + if (StringUtil.hasText(string)) { + try { + return Integer.parseInt(string); + } catch (Exception e) { + Log.log(e); + } + } + return dflt; + } + + + + public void put(String id, boolean enable) throws Exception { + put(id, Boolean.toString(enable)); + } + + public IPropertyStore getBackingStore() { + return backingStore; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/PropertyStores.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/PropertyStores.java new file mode 100644 index 000000000..e351032db --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/pstore/PropertyStores.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2015, 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.pstore; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ProjectScope; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.jface.preference.IPreferenceStore; +import org.springsource.ide.eclipse.commons.livexp.util.Log; + +/** + * Provides static helper methods to create IPropertyStore instances, + */ +public class PropertyStores { + + private static final String DEFAULT_QUALIFIER = "microservice-metadata"; + + public static IScopedPropertyStore createForProjects() { + return createForProjects(DEFAULT_QUALIFIER); + } + + public static IScopedPropertyStore createForProjects(String qualifier) { + return new PreferenceBasedStore() { + @Override + protected IEclipsePreferences createPrefs(IProject p) { + IEclipsePreferences prefs = new ProjectScope(p).getNode(qualifier); + return prefs; + } + }; + } + + public static IPropertyStore createForScope(final S scope, final IScopedPropertyStore scopedStore) { + Assert.isNotNull(scopedStore); + return new IPropertyStore() { + + @Override + public void put(String key, String value) throws Exception { + scopedStore.put(scope, key, value); + } + + @Override + public String get(String key) { + return scopedStore.get(scope, key); + } + }; + } + + public static PropertyStoreApi createApi(IPropertyStore backingStore) { + return new PropertyStoreApi(backingStore); + } + + public static IPropertyStore createSubStore(final String subStoreId, final IPropertyStore backingStore) { + return new IPropertyStore() { + + private String subkey(String key) { + return subStoreId+":"+key; + } + + @Override + public void put(String key, String value) throws Exception { + backingStore.put(subkey(key), value); + } + + @Override + public String get(String key) { + return backingStore.get(subkey(key)); + } + }; + } + + public static IPropertyStore createFor(final ILaunchConfiguration launchConf) { + return new IPropertyStore() { + + private static final String prefix = "boot.dash."; + private ILaunchConfiguration conf = launchConf; + + @Override + public synchronized void put(String key, String value) throws Exception { + ILaunchConfigurationWorkingCopy wc = conf.getWorkingCopy(); + wc.setAttribute(prefix+key, value); + wc.doSave(); + } + + + @Override + public String get(String key) { + try { + return conf.getAttribute(prefix+key,(String)null); + } catch (Exception e) { + Log.log(e); + }; + return null; + } + }; + } + + public static IPropertyStore backedBy(final IPreferenceStore preferenceStore) { + return new IPropertyStore() { + @Override + public void put(String key, String value) throws Exception { + if (value==null) { + preferenceStore.setToDefault(key); + } else { + preferenceStore.setValue(key, value); + } + } + @Override + public String get(String key) { + if (preferenceStore.contains(key)) { + return preferenceStore.getString(key); + } + return null; + } + }; + } + + public static IPropertyStore createPrivateStore(IPath append) { + return new PropertyFileStore(append.toFile()); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/templates/TemplateProcessor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/templates/TemplateProcessor.java new file mode 100644 index 000000000..84e9a5b20 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/templates/TemplateProcessor.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright (c) 2012, 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.templates; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.util.HashMap; +import java.util.Map; + +import org.apache.log4j.Logger; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.springsource.ide.eclipse.commons.core.FileUtil; + +/** + * @author Terry Denney + * @author Leo Dos Santos + * @author Christian Dupuis + * @author Kaitlin Duck Sherwood + * @author Kris De Volder (Add recursion over directories). + */ +public class TemplateProcessor { + + private static Logger logger = Logger.getLogger(TemplateProcessor.class); + + protected Map replacementContext = new HashMap(); + +// protected SpringVersionProcessor springProcessor; + + public TemplateProcessor(Map replacementContext) { + this.replacementContext = replacementContext; + } + +// public TemplateProcessor(Map replacementContext, SpringVersion springVersion) { +// this.replacementContext = replacementContext; +// +// if (springVersion != null && !springVersion.equals(SpringVersion.DEFAULT)) { +// this.springProcessor = new SpringVersionProcessor(springVersion.getVersion()); +// } +// } + + public void process(File source, File target) throws IOException { + if (FileUtil.isBinaryFile(source)) { + logger.debug("Copying binary file " + source); + FileUtil.copy(source, target); + } else if (source.isDirectory()) { + try { + FileUtil.copyDirectory(source, target, new NullProgressMonitor()); + } + catch (CoreException e) { + logger.error("Problem while attempting to copy template directory", e); + } + String[] names = source.list(); + if (names!=null) { + for (String childName : names) { + File childSource = new File(source, childName); + File childTarget = new File(target, childName); + process(childSource, childTarget); + } + } + } + else { + logger.debug("Template processing text file " + source); + process(new FileReader(source), new FileWriter(target)); + } + } + + public void process(Reader reader, Writer writer) throws IOException { + BufferedReader bufferedReader = new BufferedReader(reader); + BufferedWriter bufferedWriter = new BufferedWriter(writer); + + while (true) { + String line = bufferedReader.readLine(); + if (line == null) { + break; + } + line = replaceTokens(line); + bufferedWriter.write(line); + bufferedWriter.write('\n'); + } + bufferedWriter.flush(); + } + + public String replaceTokens(String input) { + String topLevelPackageStr = replacementContext.get("||top-level-package||"); + if (topLevelPackageStr != null) { + if (topLevelPackageStr.startsWith("&&")) { + topLevelPackageStr = topLevelPackageStr.substring(2); + } + topLevelPackageStr = topLevelPackageStr.replaceAll("&&", "."); + String userTopLevelPackageStr = replacementContext.get("||user-top-level-package||"); + if (userTopLevelPackageStr.startsWith("&&")) { + userTopLevelPackageStr = userTopLevelPackageStr.substring(2); + } + + if (input.contains(topLevelPackageStr) && userTopLevelPackageStr != null) { + int index = input.indexOf(topLevelPackageStr); + String newInput = input.substring(0, index) + userTopLevelPackageStr.replaceAll("&&", "."); + + if (index + topLevelPackageStr.length() < input.length()) { + newInput += input.substring(index + topLevelPackageStr.length()); + } + + input = newInput; + } + } + + for (String token : replacementContext.keySet()) { + String replacement = replacementContext.get(token); + input = input.replace(token, replacement); + } + + if (input.indexOf("my") >= 0) { + logger.warn("May have failed to replace token " + input); + } + return input; + } + + public String replacePathForDirectory(String path, char separator) { + String originalTopLevelPackageStr = replacementContext.get("||top-level-package||"); + if (originalTopLevelPackageStr != null) { + originalTopLevelPackageStr = originalTopLevelPackageStr.replaceAll("&&", "\\" + separator); + + String userTopLevelPackageStr = replacementContext.get("||user-top-level-package||"); + + if (path.contains(originalTopLevelPackageStr) && userTopLevelPackageStr != null) { + int index = path.indexOf(originalTopLevelPackageStr); + String replacementString = userTopLevelPackageStr.replaceAll("&&", "\\" + separator); + String newPath = path.substring(0, index) + replacementString; + + if (index + 1 + originalTopLevelPackageStr.length() < path.length()) { + newPath += path.substring(index + originalTopLevelPackageStr.length()); + } + + return newPath; + } + } + + return null; + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/CollectionUtils.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/CollectionUtils.java new file mode 100644 index 000000000..be655c6c9 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/CollectionUtils.java @@ -0,0 +1,561 @@ +/* + * Copyright 2002-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springsource.ide.eclipse.commons.core.util; + +import java.util.Enumeration; +import java.util.Map; +import java.util.Properties; + +/** + * Miscellaneous collection utility methods. + * Mainly for internal use within the framework. + * + * @author Juergen Hoeller + * @author Rob Harrop + * @author Arjen Poutsma + * @since 1.1.3 + */ +public abstract class CollectionUtils { + +// /** +// * Return {@code true} if the supplied Collection is {@code null} or empty. +// * Otherwise, return {@code false}. +// * @param collection the Collection to check +// * @return whether the given Collection is empty +// */ +// public static boolean isEmpty(@Nullable Collection collection) { +// return (collection == null || collection.isEmpty()); +// } +// +// /** +// * Return {@code true} if the supplied Map is {@code null} or empty. +// * Otherwise, return {@code false}. +// * @param map the Map to check +// * @return whether the given Map is empty +// */ +// public static boolean isEmpty(@Nullable Map map) { +// return (map == null || map.isEmpty()); +// } +// +// /** +// * Convert the supplied array into a List. A primitive array gets converted +// * into a List of the appropriate wrapper type. +// *

NOTE: Generally prefer the standard {@link Arrays#asList} method. +// * This {@code arrayToList} method is just meant to deal with an incoming Object +// * value that might be an {@code Object[]} or a primitive array at runtime. +// *

A {@code null} source value will be converted to an empty List. +// * @param source the (potentially primitive) array +// * @return the converted List result +// * @see ObjectUtils#toObjectArray(Object) +// * @see Arrays#asList(Object[]) +// */ +// @SuppressWarnings("rawtypes") +// public static List arrayToList(@Nullable Object source) { +// return Arrays.asList(ObjectUtils.toObjectArray(source)); +// } +// +// /** +// * Merge the given array into the given Collection. +// * @param array the array to merge (may be {@code null}) +// * @param collection the target Collection to merge the array into +// */ +// @SuppressWarnings("unchecked") +// public static void mergeArrayIntoCollection(@Nullable Object array, Collection collection) { +// Object[] arr = ObjectUtils.toObjectArray(array); +// for (Object elem : arr) { +// collection.add((E) elem); +// } +// } +// + /** + * Merge the given Properties instance into the given Map, + * copying all properties (key-value pairs) over. + *

Uses {@code Properties.propertyNames()} to even catch + * default properties linked into the original Properties instance. + * @param props the Properties instance to merge (may be {@code null}) + * @param map the target Map to merge the properties into + */ + @SuppressWarnings("unchecked") + public static void mergePropertiesIntoMap(/*@Nullable*/ Properties props, Map map) { + if (props != null) { + for (Enumeration en = props.propertyNames(); en.hasMoreElements();) { + String key = (String) en.nextElement(); + Object value = props.get(key); + if (value == null) { + // Allow for defaults fallback or potentially overridden accessor... + value = props.getProperty(key); + } + map.put((K) key, (V) value); + } + } + } +// +// +// /** +// * Check whether the given Iterator contains the given element. +// * @param iterator the Iterator to check +// * @param element the element to look for +// * @return {@code true} if found, {@code false} otherwise +// */ +// public static boolean contains(@Nullable Iterator iterator, Object element) { +// if (iterator != null) { +// while (iterator.hasNext()) { +// Object candidate = iterator.next(); +// if (ObjectUtils.nullSafeEquals(candidate, element)) { +// return true; +// } +// } +// } +// return false; +// } +// +// /** +// * Check whether the given Enumeration contains the given element. +// * @param enumeration the Enumeration to check +// * @param element the element to look for +// * @return {@code true} if found, {@code false} otherwise +// */ +// public static boolean contains(@Nullable Enumeration enumeration, Object element) { +// if (enumeration != null) { +// while (enumeration.hasMoreElements()) { +// Object candidate = enumeration.nextElement(); +// if (ObjectUtils.nullSafeEquals(candidate, element)) { +// return true; +// } +// } +// } +// return false; +// } +// +// /** +// * Check whether the given Collection contains the given element instance. +// *

Enforces the given instance to be present, rather than returning +// * {@code true} for an equal element as well. +// * @param collection the Collection to check +// * @param element the element to look for +// * @return {@code true} if found, {@code false} otherwise +// */ +// public static boolean containsInstance(@Nullable Collection collection, Object element) { +// if (collection != null) { +// for (Object candidate : collection) { +// if (candidate == element) { +// return true; +// } +// } +// } +// return false; +// } +// +// /** +// * Return {@code true} if any element in '{@code candidates}' is +// * contained in '{@code source}'; otherwise returns {@code false}. +// * @param source the source Collection +// * @param candidates the candidates to search for +// * @return whether any of the candidates has been found +// */ +// public static boolean containsAny(Collection source, Collection candidates) { +// if (isEmpty(source) || isEmpty(candidates)) { +// return false; +// } +// for (Object candidate : candidates) { +// if (source.contains(candidate)) { +// return true; +// } +// } +// return false; +// } +// +// /** +// * Return the first element in '{@code candidates}' that is contained in +// * '{@code source}'. If no element in '{@code candidates}' is present in +// * '{@code source}' returns {@code null}. Iteration order is +// * {@link Collection} implementation specific. +// * @param source the source Collection +// * @param candidates the candidates to search for +// * @return the first present object, or {@code null} if not found +// */ +// @SuppressWarnings("unchecked") +// @Nullable +// public static E findFirstMatch(Collection source, Collection candidates) { +// if (isEmpty(source) || isEmpty(candidates)) { +// return null; +// } +// for (Object candidate : candidates) { +// if (source.contains(candidate)) { +// return (E) candidate; +// } +// } +// return null; +// } +// +// /** +// * Find a single value of the given type in the given Collection. +// * @param collection the Collection to search +// * @param type the type to look for +// * @return a value of the given type found if there is a clear match, +// * or {@code null} if none or more than one such value found +// */ +// @SuppressWarnings("unchecked") +// @Nullable +// public static T findValueOfType(Collection collection, @Nullable Class type) { +// if (isEmpty(collection)) { +// return null; +// } +// T value = null; +// for (Object element : collection) { +// if (type == null || type.isInstance(element)) { +// if (value != null) { +// // More than one value found... no clear single value. +// return null; +// } +// value = (T) element; +// } +// } +// return value; +// } +// +// /** +// * Find a single value of one of the given types in the given Collection: +// * searching the Collection for a value of the first type, then +// * searching for a value of the second type, etc. +// * @param collection the collection to search +// * @param types the types to look for, in prioritized order +// * @return a value of one of the given types found if there is a clear match, +// * or {@code null} if none or more than one such value found +// */ +// @Nullable +// public static Object findValueOfType(Collection collection, Class[] types) { +// if (isEmpty(collection) || ObjectUtils.isEmpty(types)) { +// return null; +// } +// for (Class type : types) { +// Object value = findValueOfType(collection, type); +// if (value != null) { +// return value; +// } +// } +// return null; +// } +// +// /** +// * Determine whether the given Collection only contains a single unique object. +// * @param collection the Collection to check +// * @return {@code true} if the collection contains a single reference or +// * multiple references to the same instance, {@code false} otherwise +// */ +// public static boolean hasUniqueObject(Collection collection) { +// if (isEmpty(collection)) { +// return false; +// } +// boolean hasCandidate = false; +// Object candidate = null; +// for (Object elem : collection) { +// if (!hasCandidate) { +// hasCandidate = true; +// candidate = elem; +// } +// else if (candidate != elem) { +// return false; +// } +// } +// return true; +// } +// +// /** +// * Find the common element type of the given Collection, if any. +// * @param collection the Collection to check +// * @return the common element type, or {@code null} if no clear +// * common type has been found (or the collection was empty) +// */ +// @Nullable +// public static Class findCommonElementType(Collection collection) { +// if (isEmpty(collection)) { +// return null; +// } +// Class candidate = null; +// for (Object val : collection) { +// if (val != null) { +// if (candidate == null) { +// candidate = val.getClass(); +// } +// else if (candidate != val.getClass()) { +// return null; +// } +// } +// } +// return candidate; +// } +// +// /** +// * Retrieve the last element of the given Set, using {@link SortedSet#last()} +// * or otherwise iterating over all elements (assuming a linked set). +// * @param set the Set to check (may be {@code null} or empty) +// * @return the last element, or {@code null} if none +// * @since 5.0.3 +// * @see SortedSet +// * @see LinkedHashMap#keySet() +// * @see java.util.LinkedHashSet +// */ +// @Nullable +// public static T lastElement(@Nullable Set set) { +// if (isEmpty(set)) { +// return null; +// } +// if (set instanceof SortedSet) { +// return ((SortedSet) set).last(); +// } +// +// // Full iteration necessary... +// Iterator it = set.iterator(); +// T last = null; +// while (it.hasNext()) { +// last = it.next(); +// } +// return last; +// } +// +// /** +// * Retrieve the last element of the given List, accessing the highest index. +// * @param list the List to check (may be {@code null} or empty) +// * @return the last element, or {@code null} if none +// * @since 5.0.3 +// */ +// @Nullable +// public static T lastElement(@Nullable List list) { +// if (isEmpty(list)) { +// return null; +// } +// return list.get(list.size() - 1); +// } +// +// /** +// * Marshal the elements from the given enumeration into an array of the given type. +// * Enumeration elements must be assignable to the type of the given array. The array +// * returned will be a different instance than the array given. +// */ +// public static A[] toArray(Enumeration enumeration, A[] array) { +// ArrayList elements = new ArrayList<>(); +// while (enumeration.hasMoreElements()) { +// elements.add(enumeration.nextElement()); +// } +// return elements.toArray(array); +// } +// +// /** +// * Adapt an {@link Enumeration} to an {@link Iterator}. +// * @param enumeration the original {@code Enumeration} +// * @return the adapted {@code Iterator} +// */ +// public static Iterator toIterator(@Nullable Enumeration enumeration) { +// return (enumeration != null ? new EnumerationIterator<>(enumeration) : Collections.emptyIterator()); +// } +// +// /** +// * Adapt a {@code Map>} to an {@code MultiValueMap}. +// * @param map the original map +// * @return the multi-value map +// * @since 3.1 +// */ +// public static MultiValueMap toMultiValueMap(Map> map) { +// return new MultiValueMapAdapter<>(map); +// } +// +// /** +// * Return an unmodifiable view of the specified multi-value map. +// * @param map the map for which an unmodifiable view is to be returned. +// * @return an unmodifiable view of the specified multi-value map. +// * @since 3.1 +// */ +// @SuppressWarnings("unchecked") +// public static MultiValueMap unmodifiableMultiValueMap(MultiValueMap map) { +// Assert.notNull(map, "'map' must not be null"); +// Map> result = new LinkedHashMap<>(map.size()); +// map.forEach((key, value) -> { +// List values = Collections.unmodifiableList(value); +// result.put(key, (List) values); +// }); +// Map> unmodifiableMap = Collections.unmodifiableMap(result); +// return toMultiValueMap(unmodifiableMap); +// } +// +// +// /** +// * Iterator wrapping an Enumeration. +// */ +// private static class EnumerationIterator implements Iterator { +// +// private final Enumeration enumeration; +// +// public EnumerationIterator(Enumeration enumeration) { +// this.enumeration = enumeration; +// } +// +// @Override +// public boolean hasNext() { +// return this.enumeration.hasMoreElements(); +// } +// +// @Override +// public E next() { +// return this.enumeration.nextElement(); +// } +// +// @Override +// public void remove() throws UnsupportedOperationException { +// throw new UnsupportedOperationException("Not supported"); +// } +// } +// +// +// /** +// * Adapts a Map to the MultiValueMap contract. +// */ +// @SuppressWarnings("serial") +// private static class MultiValueMapAdapter implements MultiValueMap, Serializable { +// +// private final Map> map; +// +// public MultiValueMapAdapter(Map> map) { +// Assert.notNull(map, "'map' must not be null"); +// this.map = map; +// } +// +// @Override +// @Nullable +// public V getFirst(K key) { +// List values = this.map.get(key); +// return (values != null ? values.get(0) : null); +// } +// +// @Override +// public void add(K key, @Nullable V value) { +// List values = this.map.computeIfAbsent(key, k -> new LinkedList<>()); +// values.add(value); +// } +// +// @Override +// public void addAll(K key, List values) { +// List currentValues = this.map.computeIfAbsent(key, k -> new LinkedList<>()); +// currentValues.addAll(values); +// } +// +// @Override +// public void addAll(MultiValueMap values) { +// for (Entry> entry : values.entrySet()) { +// addAll(entry.getKey(), entry.getValue()); +// } +// } +// +// @Override +// public void set(K key, @Nullable V value) { +// List values = new LinkedList<>(); +// values.add(value); +// this.map.put(key, values); +// } +// +// @Override +// public void setAll(Map values) { +// values.forEach(this::set); +// } +// +// @Override +// public Map toSingleValueMap() { +// LinkedHashMap singleValueMap = new LinkedHashMap<>(this.map.size()); +// this.map.forEach((key, value) -> singleValueMap.put(key, value.get(0))); +// return singleValueMap; +// } +// +// @Override +// public int size() { +// return this.map.size(); +// } +// +// @Override +// public boolean isEmpty() { +// return this.map.isEmpty(); +// } +// +// @Override +// public boolean containsKey(Object key) { +// return this.map.containsKey(key); +// } +// +// @Override +// public boolean containsValue(Object value) { +// return this.map.containsValue(value); +// } +// +// @Override +// public List get(Object key) { +// return this.map.get(key); +// } +// +// @Override +// public List put(K key, List value) { +// return this.map.put(key, value); +// } +// +// @Override +// public List remove(Object key) { +// return this.map.remove(key); +// } +// +// @Override +// public void putAll(Map> map) { +// this.map.putAll(map); +// } +// +// @Override +// public void clear() { +// this.map.clear(); +// } +// +// @Override +// public Set keySet() { +// return this.map.keySet(); +// } +// +// @Override +// public Collection> values() { +// return this.map.values(); +// } +// +// @Override +// public Set>> entrySet() { +// return this.map.entrySet(); +// } +// +// @Override +// public boolean equals(Object other) { +// if (this == other) { +// return true; +// } +// return this.map.equals(other); +// } +// +// @Override +// public int hashCode() { +// return this.map.hashCode(); +// } +// +// @Override +// public String toString() { +// return this.map.toString(); +// } +// } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/FileCopyUtils.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/FileCopyUtils.java new file mode 100644 index 000000000..1a52f0926 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/FileCopyUtils.java @@ -0,0 +1,241 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springsource.ide.eclipse.commons.core.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.log4j.lf5.util.StreamUtils; +import org.eclipse.core.runtime.Assert; + +/** + * Simple utility methods for file and stream copying. All copy methods use a block size + * of 4096 bytes, and close all affected streams when done. A variation of the copy + * methods from this class that leave streams open can be found in {@link StreamUtils}. + * + *

Mainly for use within the framework, but also useful for application code. + * + * @author Juergen Hoeller + * @since 06.10.2003 + * @see StreamUtils + * @see FileSystemUtils + */ +public abstract class FileCopyUtils { + +// /** +// * The default buffer size used when copying bytes. +// */ +// public static final int BUFFER_SIZE = StreamUtils.BUFFER_SIZE; +// +// +// //--------------------------------------------------------------------- +// // Copy methods for java.io.File +// //--------------------------------------------------------------------- +// +// /** +// * Copy the contents of the given input File to the given output File. +// * @param in the file to copy from +// * @param out the file to copy to +// * @return the number of bytes copied +// * @throws IOException in case of I/O errors +// */ +// public static int copy(File in, File out) throws IOException { +// Assert.notNull(in, "No input File specified"); +// Assert.notNull(out, "No output File specified"); +// return copy(Files.newInputStream(in.toPath()), Files.newOutputStream(out.toPath())); +// } +// +// /** +// * Copy the contents of the given byte array to the given output File. +// * @param in the byte array to copy from +// * @param out the file to copy to +// * @throws IOException in case of I/O errors +// */ +// public static void copy(byte[] in, File out) throws IOException { +// Assert.notNull(in, "No input byte array specified"); +// Assert.notNull(out, "No output File specified"); +// copy(new ByteArrayInputStream(in), Files.newOutputStream(out.toPath())); +// } +// +// /** +// * Copy the contents of the given input File into a new byte array. +// * @param in the file to copy from +// * @return the new byte array that has been copied to +// * @throws IOException in case of I/O errors +// */ +// public static byte[] copyToByteArray(File in) throws IOException { +// Assert.notNull(in, "No input File specified"); +// return copyToByteArray(Files.newInputStream(in.toPath())); +// } +// +// + //--------------------------------------------------------------------- + // Copy methods for java.io.InputStream / java.io.OutputStream + //--------------------------------------------------------------------- + + /** + * Copy the contents of the given InputStream to the given OutputStream. + * Closes both streams when done. + * @param in the stream to copy from + * @param out the stream to copy to + * @throws IOException in case of I/O errors + */ + public static void copy(InputStream in, OutputStream out) throws IOException { + Assert.isNotNull(in, "No InputStream specified"); + Assert.isNotNull(out, "No OutputStream specified"); + + try { + StreamUtils.copy(in, out); + } + finally { + try { + in.close(); + } + catch (IOException ex) { + } + try { + out.close(); + } + catch (IOException ex) { + } + } + } +// +// /** +// * Copy the contents of the given byte array to the given OutputStream. +// * Closes the stream when done. +// * @param in the byte array to copy from +// * @param out the OutputStream to copy to +// * @throws IOException in case of I/O errors +// */ +// public static void copy(byte[] in, OutputStream out) throws IOException { +// Assert.notNull(in, "No input byte array specified"); +// Assert.notNull(out, "No OutputStream specified"); +// +// try { +// out.write(in); +// } +// finally { +// try { +// out.close(); +// } +// catch (IOException ex) { +// } +// } +// } +// +// /** +// * Copy the contents of the given InputStream into a new byte array. +// * Closes the stream when done. +// * @param in the stream to copy from (may be {@code null} or empty) +// * @return the new byte array that has been copied to (possibly empty) +// * @throws IOException in case of I/O errors +// */ +// public static byte[] copyToByteArray(@Nullable InputStream in) throws IOException { +// if (in == null) { +// return new byte[0]; +// } +// +// ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE); +// copy(in, out); +// return out.toByteArray(); +// } +// +// +// //--------------------------------------------------------------------- +// // Copy methods for java.io.Reader / java.io.Writer +// //--------------------------------------------------------------------- +// +// /** +// * Copy the contents of the given Reader to the given Writer. +// * Closes both when done. +// * @param in the Reader to copy from +// * @param out the Writer to copy to +// * @return the number of characters copied +// * @throws IOException in case of I/O errors +// */ +// public static int copy(Reader in, Writer out) throws IOException { +// Assert.notNull(in, "No Reader specified"); +// Assert.notNull(out, "No Writer specified"); +// +// try { +// int byteCount = 0; +// char[] buffer = new char[BUFFER_SIZE]; +// int bytesRead = -1; +// while ((bytesRead = in.read(buffer)) != -1) { +// out.write(buffer, 0, bytesRead); +// byteCount += bytesRead; +// } +// out.flush(); +// return byteCount; +// } +// finally { +// try { +// in.close(); +// } +// catch (IOException ex) { +// } +// try { +// out.close(); +// } +// catch (IOException ex) { +// } +// } +// } +// +// /** +// * Copy the contents of the given String to the given output Writer. +// * Closes the writer when done. +// * @param in the String to copy from +// * @param out the Writer to copy to +// * @throws IOException in case of I/O errors +// */ +// public static void copy(String in, Writer out) throws IOException { +// Assert.notNull(in, "No input String specified"); +// Assert.notNull(out, "No Writer specified"); +// +// try { +// out.write(in); +// } +// finally { +// try { +// out.close(); +// } +// catch (IOException ex) { +// } +// } +// } +// +// /** +// * Copy the contents of the given Reader into a String. +// * Closes the reader when done. +// * @param in the reader to copy from (may be {@code null} or empty) +// * @return the String that has been copied to (possibly empty) +// * @throws IOException in case of I/O errors +// */ +// public static String copyToString(@Nullable Reader in) throws IOException { +// if (in == null) { +// return ""; +// } +// +// StringWriter out = new StringWriter(); +// copy(in, out); +// return out.toString(); +// } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/FuzzyMatcher.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/FuzzyMatcher.java new file mode 100644 index 000000000..c4eaada58 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/FuzzyMatcher.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2015 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.eclipse.jface.text.IRegion; + +/** + * @author Kris De Volder + */ +public class FuzzyMatcher { + + private static class Region implements IRegion { + public Region(int start, int end) { + super(); + this.start = start; + this.end = end; + } + int start; + int end; + @Override + public int getLength() { + return end-start; + } + @Override + public int getOffset() { + return start; + } + @Override + public String toString() { + return "Region["+start+".."+end+"]"; + } + } + + public static Collection highlights(CharSequence query, String data) { + List highlights = new ArrayList<>(); + int qpos = 0; + int dpos = 0; + int qlen = query.length(); + int dlen = data.length(); + while (qpos highlights, int nextMatch) { + if (highlights.isEmpty()) { + highlights.add(new Region(nextMatch, nextMatch+1)); + } + Region lastRegion = (Region) highlights.get(highlights.size()-1); + if (lastRegion.getOffset()+lastRegion.getLength()==nextMatch) { + lastRegion.end++; + } else { + highlights.add(new Region(nextMatch, nextMatch+1)); + } + } + + /** + * Match given pattern with a given data. The data is considered a 'match' for the + * pattern if all characters in the pattern can be found in the data, in the + * same order but with possible 'gaps' in between. + *

+ * The function returns 0. when the pattern doesn't match the data and a non-zero + * 'score' when it does. The higher the score, the better the match is considered to + * be. + */ + public static double matchScore(CharSequence pattern, String data) { + int ppos = 0; //pos of next char in pattern to look for + int dpos = 0; //pos of next char in data not yet matched + int gaps = 0; //number of 'gaps' in the match. A gap is any non-empty run of consecutive characters in the data that are not used by the match + int skips = 0; //number of skipped characters. This is the sum of the length of all the gaps. + int plen = pattern.length(); + int dlen = data.length(); + if (plen>dlen) { + return 0.0; + } + while (ppos=dlen) { + //still chars left in pattern but no more data + return 0.0; + } + char c = pattern.charAt(ppos++); + int foundCharAt = data.indexOf(c, dpos); + if (foundCharAt>=0) { + if (foundCharAt>dpos) { + gaps++; + skips+=foundCharAt-dpos; + } + dpos = foundCharAt+1; + } else { + return 0.0; + } + } + //end of pattern reached. All matched. + if (dpos more extra = worse score + } + return score(gaps, skips, pattern); + } + + private static double score(int gaps, int skips, CharSequence pattern) { + if (gaps==0) { + //gaps == 0 means a prefix match, ignore 'skips' at end of String and just sort + // alphabetic (see STS-4049) + return 0.5+pattern.length(); //all scored equally, assumes using a 'stable' sorter. + } else { + double badness = 1+gaps + skips/1000.0; // higher is worse + return 1.0/badness + pattern.length(); //higher is better + } + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/IOUtil.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/IOUtil.java new file mode 100644 index 000000000..8e3198491 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/IOUtil.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.util; + +import java.io.IOException; +import java.io.InputStream; + +public class IOUtil { + + private static final int BUF_SIZE = 1024; + + /** + * Consume all data in an input stream, discdaring the data and closing the stream upon completion. + */ + public static void consume(InputStream stream) throws IOException { + try { + byte[] buf = new byte[BUF_SIZE]; + while (stream.read(buf)>=0) {} + } finally { + try { + stream.close(); + } catch (IOException e) { + //ignore (or it may mask a more important / interesting error in the body of the try + } + } + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/MultiplexingOutputStream.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/MultiplexingOutputStream.java new file mode 100644 index 000000000..86f55b6bf --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/MultiplexingOutputStream.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.util; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * A wrapper around multiple output streams that mulitplexes any output written to this Stream to all + * the wrapped Streams. + * @author Kris De Volder + */ +public class MultiplexingOutputStream extends OutputStream { + + OutputStream[] streams = null; + + public MultiplexingOutputStream(OutputStream... streams) { + super(); + if (streams==null) { + this.streams = new OutputStream[0]; + } else { + this.streams = streams; + } + } + + /** + * Writes the byte b to each wrapped OutputStream. + *

+ * If one of the Streams throws an Exception this exception is + * propagated. No guarantees are made in this case that output + * is written to the other Streams. + */ + @Override + public void write(int b) throws IOException { + for (OutputStream s : streams) { + s.write(b); + } + } + + /** + * Writes to each wrapped OutputStream. + *

+ * If one of the Streams throws an Exception this exception is + * propagated. No guarantees are made in this case that output + * is written to the other Streams. + */ + @Override + public void write(byte[] b, int off, int len) throws IOException { + for (OutputStream s : streams) { + s.write(b, off, len); + } + } + + /** + * Attempt to close all of the underlying Streams. If any of the Streams + * throws an exception it is guaranteed that at least one of those + * exceptions will be propagated. + *

+ * Even if one of the streams throws an Exception, we will still + * attempt to close the other Streams. + */ + @Override + public void close() throws IOException { + super.close(); + Throwable caught = null; + for (OutputStream s : streams) { + try { + s.close(); + } catch (Throwable e) { + caught = e; + } + } + if (caught!=null) { + if (caught instanceof IOException) { + throw (IOException)caught; + } else { + // If not a Declared exception it must be an unchecked exception. + throw (Error)caught; + } + } + } + + @Override + public void flush() throws IOException { + for (OutputStream s : streams) { + s.flush(); + } + } + + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/NameGenerator.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/NameGenerator.java new file mode 100644 index 000000000..277c9c0d2 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/NameGenerator.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.util; + +/** + * Simple name generator implementation + * + * @author Alex Boyko + * @author Kris De Volder + */ +public class NameGenerator { + + private static final String delimiter = "-"; + + private String prefix; + private int number = 0; + + public NameGenerator(String previousName) { + prefix = previousName.replace(' ', '-'); //See: https://github.com/spring-projects/spring-ide/issues/230 + int d = previousName.lastIndexOf(delimiter); + if (d>=0) { + try { + String numString = previousName.substring(d+1); + number = Integer.parseInt(numString); + prefix = previousName.substring(0, d); + } catch (NumberFormatException e) { + //ignore + } + } + } + + public String generateNext() { + StringBuilder sb = new StringBuilder(); + sb.append(prefix); + sb.append(delimiter); + sb.append(++number); + return sb.toString(); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/NatureUtils.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/NatureUtils.java new file mode 100644 index 000000000..b8b29a391 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/NatureUtils.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2012, 2015 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.util; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; + +/** + * @author Kris De Volder + */ +public class NatureUtils { + + /** + * Make sure a project has a number of required natures. If the natures are not yet present, add them now. + * @throws CoreException + */ + public static void ensure(IProject project, IProgressMonitor mon, String... reqNatures) throws CoreException { + IProjectDescription desc = project.getDescription(); + String[] oldNaturesArr = desc.getNatureIds(); + Set natures = new LinkedHashSet(); + for (String n : reqNatures) { + natures.add(n); + } + for (String n : oldNaturesArr) { + natures.add(n); + } + if (natures.size()>oldNaturesArr.length) { + //Some natures got added + desc.setNatureIds(natures.toArray(new String[natures.size()])); + project.setDescription(desc, mon); + } else { + //No new natures added, but need to set it to force desired ordering + desc.setNatureIds(natures.toArray(new String[natures.size()])); + project.setDescription(desc, IResource.AVOID_NATURE_CONFIG, mon); + } + } + + /** + * Removes a nature from a project. This does nothing if the project doesn't have the nature. + */ + public static void remove(IProject project, String natureId, IProgressMonitor mon) throws CoreException { + IProjectDescription desc = project.getDescription(); + String[] oldNaturesArr = desc.getNatureIds(); + Set natures = new LinkedHashSet(); + for (String n : oldNaturesArr) { + if (!n.equals(natureId)) { + natures.add(n); + } + } + if (natures.size()!=oldNaturesArr.length) { + //Something removed + desc.setNatureIds(natures.toArray(new String[natures.size()])); + project.setDescription(desc, mon); + } + + } + + public static boolean hasNature(IProject p, String natureId) { + try { + return p!=null && p.isAccessible() && p.hasNature(natureId); + } catch (CoreException e) { + CorePlugin.log(e); + } + return false; + } + + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/OsUtils.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/OsUtils.java new file mode 100644 index 000000000..17733ea26 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/OsUtils.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.util; + +import static java.nio.file.attribute.PosixFilePermission.GROUP_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.GROUP_READ; +import static java.nio.file.attribute.PosixFilePermission.GROUP_WRITE; +import static java.nio.file.attribute.PosixFilePermission.OTHERS_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ; +import static java.nio.file.attribute.PosixFilePermission.OTHERS_WRITE; +import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; + +import java.nio.file.attribute.PosixFilePermission; +import java.util.EnumSet; +import java.util.Set; + +/** + * @author Kris De Volder + * + * @since 2.8 + */ +public class OsUtils { + + public static boolean isWindows() { + return System.getProperty("os.name").contains("Windows"); + } + + private static final PosixFilePermission[] decodeMap = { + //This is exactly the reverse order as in the enum. + //But it seems safer not to rely on that. + OTHERS_EXECUTE, + OTHERS_WRITE, + OTHERS_READ, + GROUP_EXECUTE, + GROUP_WRITE, + GROUP_READ, + OWNER_EXECUTE, + OWNER_WRITE, + OWNER_READ + }; + + public static Set posixFilePermissions(int mode) { + int mask = 1; + Set perms = EnumSet.noneOf(PosixFilePermission.class); + for (PosixFilePermission flag : decodeMap) { + if (flag!=null && (mask & mode) != 0) { + perms.add(flag); + } + mask = mask << 1; + } + return perms; + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/ProcessUtils.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/ProcessUtils.java new file mode 100644 index 000000000..e8ac185e4 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/ProcessUtils.java @@ -0,0 +1,94 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.util; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.List; + +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; +import javax.management.remote.JMXServiceURL; + +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; + +import com.sun.tools.attach.AttachNotSupportedException; +import com.sun.tools.attach.VirtualMachine; +import com.sun.tools.attach.VirtualMachineDescriptor; + +/** + * Process Utilities making use of JDK tools + * + * @author Alex Boyko + * + */ +@SuppressWarnings("restriction") +public class ProcessUtils { + + /** + * Retrieves PID of a process + * @param p the process + * @return PID + */ + public static long getProcessID(Process p) { + long result = -1; + try { + Field f = p.getClass().getDeclaredField("pid"); + f.setAccessible(true); + result = f.getLong(p); + f.setAccessible(false); + } + catch (Exception ex) { + throw new UnsupportedOperationException("Process PID calculation not supported on the current platform", + ex); + } + return result; + } + + /** + * Creates JMX Connector to a process specified by its PID + * @param pid the PID + * @return JMX connector + */ + public static JMXConnector createJMXConnector(String pid) { + List vmds = VirtualMachine.list(); + VirtualMachineDescriptor vmd = vmds.stream().filter(descriptor -> descriptor.id().equals(pid)).findFirst().orElse(null); + if (vmd != null) { + try { + String agentUrl = VirtualMachine.attach(vmd).startLocalManagementAgent(); + if (agentUrl != null) { + JMXServiceURL serviceUrl = new JMXServiceURL(agentUrl); + return JMXConnectorFactory.connect(serviceUrl, null); + } + } catch (AttachNotSupportedException e) { + CorePlugin.log(e); + } catch (IOException e) { + CorePlugin.log(e); + } + } + return null; + } + + /** + * Determines whether current JDK supports utilities defined in this class + * @return true for compatible JDK + */ + public static boolean isLatestJdkForTools() { + try { + return VirtualMachine.class.getDeclaredMethod("startLocalManagementAgent") != null; + } + catch (NoSuchMethodException | SecurityException e) { + // ignore + } + return false; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/StreamUtils.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/StreamUtils.java new file mode 100644 index 000000000..41de8c003 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/StreamUtils.java @@ -0,0 +1,263 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springsource.ide.eclipse.commons.core.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.eclipse.core.runtime.Assert; + +/** + * Simple utility methods for dealing with streams. The copy methods of this class are + * similar to those defined in {@link FileCopyUtils} except that all affected streams are + * left open when done. All copy methods use a block size of 4096 bytes. + * + *

Mainly for use within the framework, but also useful for application code. + * + * @author Juergen Hoeller + * @author Phillip Webb + * @author Brian Clozel + * @since 3.2.2 + * @see FileCopyUtils + */ +public abstract class StreamUtils { + + /** + * The default buffer size used why copying bytes. + */ + public static final int BUFFER_SIZE = 4096; + +// private static final byte[] EMPTY_CONTENT = new byte[0]; +// +// +// /** +// * Copy the contents of the given InputStream into a new byte array. +// * Leaves the stream open when done. +// * @param in the stream to copy from (may be {@code null} or empty) +// * @return the new byte array that has been copied to (possibly empty) +// * @throws IOException in case of I/O errors +// */ +// public static byte[] copyToByteArray(@Nullable InputStream in) throws IOException { +// if (in == null) { +// return new byte[0]; +// } +// +// ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE); +// copy(in, out); +// return out.toByteArray(); +// } +// +// /** +// * Copy the contents of the given InputStream into a String. +// * Leaves the stream open when done. +// * @param in the InputStream to copy from (may be {@code null} or empty) +// * @return the String that has been copied to (possibly empty) +// * @throws IOException in case of I/O errors +// */ +// public static String copyToString(@Nullable InputStream in, Charset charset) throws IOException { +// if (in == null) { +// return ""; +// } +// +// StringBuilder out = new StringBuilder(); +// InputStreamReader reader = new InputStreamReader(in, charset); +// char[] buffer = new char[BUFFER_SIZE]; +// int bytesRead = -1; +// while ((bytesRead = reader.read(buffer)) != -1) { +// out.append(buffer, 0, bytesRead); +// } +// return out.toString(); +// } +// +// /** +// * Copy the contents of the given byte array to the given OutputStream. +// * Leaves the stream open when done. +// * @param in the byte array to copy from +// * @param out the OutputStream to copy to +// * @throws IOException in case of I/O errors +// */ +// public static void copy(byte[] in, OutputStream out) throws IOException { +// Assert.notNull(in, "No input byte array specified"); +// Assert.notNull(out, "No OutputStream specified"); +// +// out.write(in); +// } +// +// /** +// * Copy the contents of the given String to the given output OutputStream. +// * Leaves the stream open when done. +// * @param in the String to copy from +// * @param charset the Charset +// * @param out the OutputStream to copy to +// * @throws IOException in case of I/O errors +// */ +// public static void copy(String in, Charset charset, OutputStream out) throws IOException { +// Assert.notNull(in, "No input String specified"); +// Assert.notNull(charset, "No charset specified"); +// Assert.notNull(out, "No OutputStream specified"); +// +// Writer writer = new OutputStreamWriter(out, charset); +// writer.write(in); +// writer.flush(); +// } +// + /** + * Copy the contents of the given InputStream to the given OutputStream. + * Leaves both streams open when done. + * @param in the InputStream to copy from + * @param out the OutputStream to copy to + * @return the number of bytes copied + * @throws IOException in case of I/O errors + */ + public static int copy(InputStream in, OutputStream out) throws IOException { + Assert.isNotNull(in, "No InputStream specified"); + Assert.isNotNull(out, "No OutputStream specified"); + + int byteCount = 0; + byte[] buffer = new byte[BUFFER_SIZE]; + int bytesRead = -1; + while ((bytesRead = in.read(buffer)) != -1) { + out.write(buffer, 0, bytesRead); + byteCount += bytesRead; + } + out.flush(); + return byteCount; + } + +// /** +// * Copy a range of content of the given InputStream to the given OutputStream. +// *

If the specified range exceeds the length of the InputStream, this copies +// * up to the end of the stream and returns the actual number of copied bytes. +// *

Leaves both streams open when done. +// * @param in the InputStream to copy from +// * @param out the OutputStream to copy to +// * @param start the position to start copying from +// * @param end the position to end copying +// * @return the number of bytes copied +// * @throws IOException in case of I/O errors +// * @since 4.3 +// */ +// public static long copyRange(InputStream in, OutputStream out, long start, long end) throws IOException { +// Assert.notNull(in, "No InputStream specified"); +// Assert.notNull(out, "No OutputStream specified"); +// +// long skipped = in.skip(start); +// if (skipped < start) { +// throw new IOException("Skipped only " + skipped + " bytes out of " + start + " required"); +// } +// +// long bytesToCopy = end - start + 1; +// byte[] buffer = new byte[StreamUtils.BUFFER_SIZE]; +// while (bytesToCopy > 0) { +// int bytesRead = in.read(buffer); +// if (bytesRead == -1) { +// break; +// } +// else if (bytesRead <= bytesToCopy) { +// out.write(buffer, 0, bytesRead); +// bytesToCopy -= bytesRead; +// } +// else { +// out.write(buffer, 0, (int) bytesToCopy); +// bytesToCopy = 0; +// } +// } +// return (end - start + 1 - bytesToCopy); +// } +// +// /** +// * Drain the remaining content of the given InputStream. +// * Leaves the InputStream open when done. +// * @param in the InputStream to drain +// * @return the number of bytes read +// * @throws IOException in case of I/O errors +// * @since 4.3 +// */ +// public static int drain(InputStream in) throws IOException { +// Assert.notNull(in, "No InputStream specified"); +// byte[] buffer = new byte[BUFFER_SIZE]; +// int bytesRead = -1; +// int byteCount = 0; +// while ((bytesRead = in.read(buffer)) != -1) { +// byteCount += bytesRead; +// } +// return byteCount; +// } +// +// /** +// * Return an efficient empty {@link InputStream}. +// * @return a {@link ByteArrayInputStream} based on an empty byte array +// * @since 4.2.2 +// */ +// public static InputStream emptyInput() { +// return new ByteArrayInputStream(EMPTY_CONTENT); +// } +// +// /** +// * Return a variant of the given {@link InputStream} where calling +// * {@link InputStream#close() close()} has no effect. +// * @param in the InputStream to decorate +// * @return a version of the InputStream that ignores calls to close +// */ +// public static InputStream nonClosing(InputStream in) { +// Assert.notNull(in, "No InputStream specified"); +// return new NonClosingInputStream(in); +// } +// +// /** +// * Return a variant of the given {@link OutputStream} where calling +// * {@link OutputStream#close() close()} has no effect. +// * @param out the OutputStream to decorate +// * @return a version of the OutputStream that ignores calls to close +// */ +// public static OutputStream nonClosing(OutputStream out) { +// Assert.notNull(out, "No OutputStream specified"); +// return new NonClosingOutputStream(out); +// } +// +// +// private static class NonClosingInputStream extends FilterInputStream { +// +// public NonClosingInputStream(InputStream in) { +// super(in); +// } +// +// @Override +// public void close() throws IOException { +// } +// } +// +// +// private static class NonClosingOutputStream extends FilterOutputStream { +// +// public NonClosingOutputStream(OutputStream out) { +// super(out); +// } +// +// @Override +// public void write(byte[] b, int off, int let) throws IOException { +// // It is critical that we override this method for performance +// this.out.write(b, off, let); +// } +// +// @Override +// public void close() throws IOException { +// } +// } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/StringUtil.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/StringUtil.java new file mode 100644 index 000000000..a7a0fcdb8 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/core/util/StringUtil.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2017 Spring IDE Developers + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Spring IDE Developers - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.core.util; + +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class StringUtil { + + public static boolean hasText(String name) { + return name!=null && !name.trim().equals(""); + } + + public static String trim(String s) { + if (s!=null) { + return s.trim(); + } + return null; + } + + public static String trimEnd(String s) { + if (s!=null) { + return s.replaceAll("\\s+\\z", ""); + } + return null; + } + + public static int commonPrefixLength(CharSequence s, CharSequence t) { + int shortestStringLen = Math.min(s.length(), t.length()); + for (int i = 0; i < shortestStringLen; i++) { + if (s.charAt(i)!=t.charAt(i)) { + return i; + } + } + //no difference found upto entire length of shortest string. + return shortestStringLen; + } + + /** + * @return longest string which is a prefix of both argument Strings. + */ + public static String commonPrefix(CharSequence s, CharSequence t) { + int len = commonPrefixLength(s, t); + if (len>0) { + return s.subSequence(0,len).toString(); + } + return ""; + } + + public static String removePrefix(String s, String prefix) { + if (s.startsWith(prefix)) { + return s.substring(prefix.length()); + } else { + return s; + } + } + + public static String camelCaseToHyphens(String value) { + Matcher matcher = CAMEL_CASE_PATTERN.matcher(value); + StringBuffer result = new StringBuffer(); + int start = 0; + while (matcher.find()) { + result.append(value.substring(start, matcher.start())); + result.append(matcher.group(1)); + result.append('-'); + result.append(matcher.group(2).toLowerCase()); + start = matcher.end(); + } + result.append(value.substring(start)); + return result.toString(); + } + + private static final Pattern CAMEL_CASE_PATTERN = Pattern.compile("([^A-Z-])([A-Z])"); + + public static String arrayToCommaDelimitedString(Object[] array) { + return collectionToCommaDelimitedString(Arrays.asList(array)); + } + + public static String collectionToCommaDelimitedString(Collection items) { + StringBuilder buf = new StringBuilder(); + boolean first = true; + for (Object item : items) { + if (!first) { + buf.append(","); + } + buf.append(item); + first = false; + } + return buf.toString(); + } + + public static String upperCaseToHyphens(String v) { + if (v!=null) { + return v.toLowerCase().replace('_', '-'); + } + return null; + } + + public static String hyphensToUpperCase(String v) { + if (v!=null) { + return v.toUpperCase().replace('-', '_'); + } + return null; + } + + public static String hyphensToCamelCase(String propName, boolean startWithUpperCase) { + String [] parts = propName.split("-"); + if (startWithUpperCase) { + parts[0] = upCaseFirstChar(parts[0]); + } + StringBuilder camelCased = new StringBuilder(parts[0]); + for (int i = 1; i < parts.length; i++) { + camelCased.append(upCaseFirstChar(parts[i])); + } + return camelCased.toString(); + } + + + public static String upCaseFirstChar(String string) { + if (StringUtil.hasText(string)) { + return Character.toUpperCase(string.charAt(0)) + string.substring(1); + } + return ""; + } + + public static String datestamp() { + Date d = new Date(); + SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd"); + return f.format(d); + } +} \ No newline at end of file diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/CorePlugin.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/CorePlugin.java new file mode 100644 index 000000000..9bd815896 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/CorePlugin.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.internal.core; + +import java.util.UUID; + +import org.apache.commons.lang.StringUtils; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Plugin; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.IPreferencesService; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.prefs.Preferences; + +/** + * @author Steffen Pingel + * @author Leo Dos Santos + * @author Christian Dupuis + */ +public class CorePlugin extends Plugin { + + public static final String PLUGIN_ID = "org.springsource.ide.eclipse.commons.core"; + + private static CorePlugin plugin; + + private static final String UUID_PROPERTY_KEY = "install.id"; + + public static IStatus createErrorStatus(String message, Throwable exception) { + if (message == null) { + message = ""; + } + return new Status(IStatus.ERROR, PLUGIN_ID, 0, message, exception); + } + + public static CorePlugin getDefault() { + return plugin; + } + + public synchronized static String getUUID() { + ServiceReference ref = getDefault().getBundle().getBundleContext() + .getServiceReference(IPreferencesService.class.getName()); + IPreferencesService prefService = (IPreferencesService) getDefault().getBundle().getBundleContext() + .getService(ref); + try { + IEclipsePreferences prefs = (IEclipsePreferences) prefService.getRootNode().node(InstanceScope.SCOPE); + + Preferences p = prefs.node(PLUGIN_ID); + if (StringUtils.isEmpty(p.get(UUID_PROPERTY_KEY, ""))) { + p.put(UUID_PROPERTY_KEY, UUID.randomUUID().toString()); + } + return p.get(UUID_PROPERTY_KEY, ""); + } + finally { + if (prefService != null) { + getDefault().getBundle().getBundleContext().ungetService(ref); + } + } + } + + public static void log(IStatus status) { + getDefault().getLog().log(status); + } + + public static void log(String message, Throwable exception) { + IStatus status = createErrorStatus(message, exception); + log(status); + } + + public static void log(Throwable exception) { + log(createErrorStatus("Internal Error", exception)); + } + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + } + + @Override + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + savePluginPreferences(); + } + + public static void warn(String msg) { + log(new Status(IStatus.WARNING, PLUGIN_ID, msg)); + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/commandhistory/CommandHistory.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/commandhistory/CommandHistory.java new file mode 100644 index 000000000..5007cd56d --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/commandhistory/CommandHistory.java @@ -0,0 +1,339 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.internal.core.commandhistory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.eclipse.core.resources.ISaveContext; +import org.eclipse.core.resources.ISaveParticipant; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.springsource.ide.eclipse.commons.core.Entry; +import org.springsource.ide.eclipse.commons.core.ICommandHistory; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; + + +/** + * This provides a light-weight command history that is persisted in the user's + * workspace. + * @author Andrew Eisenberg + * @author Christian Dupuis + * @author Kris De Volder + * @since 2.5.0 + */ +public class CommandHistory implements Iterable, ICommandHistory { + + /** + * Our ISaveParticipant, responsible for persisting the CommandHistory in + * the workspace. + */ + private class CommandHistorySaver implements ISaveParticipant { + + public void doneSaving(ISaveContext context) { + } + + public void prepareToSave(ISaveContext context) throws CoreException { + } + + public void rollback(ISaveContext context) { + } + + public void saving(ISaveContext context) throws CoreException { + if (isDirty()) { + try { + save(getSavePath()); + } + catch (Exception e) { + throw new CoreException(CorePlugin.createErrorStatus("Couldn't save command history", e)); + } + context.needSaveNumber(); + } + } + + } + + private static final String HISTORY_FILE_NAME = ".commandhistory"; + + /** + * If the number of history entries exceeds maxSize, older items will be + * automatically dropped. + */ + private int maxSize = DEFAULT_MAX_SIZE; + + private boolean isDirty; + + private LinkedList history = new LinkedList(); + + private final String natureId; + + private final String historyId; + + public CommandHistory(String historyId, String natureId) { + this.natureId = natureId; + this.historyId = historyId; + } + + /** + * Create an "auto saving" CommandHistory and register it as a save + * participant with the workspace. + */ + public CommandHistory(String historyId, String natureId, boolean persist) throws CoreException { + this.natureId = natureId; + this.historyId = historyId; + if (persist) { + CommandHistorySaver saver = new CommandHistorySaver(); + // Method addSaveParticipant(String, ISaveParticipant) is deprecated + // on 3.6, but the alternative does not exist in 3.5 + ResourcesPlugin.getWorkspace().addSaveParticipant(CorePlugin.getDefault(), saver); + load(getSavePath()); + } + } + + /** + * Add an element to the history. If a similar element already exists in the + * history, the older element is removed before adding the new one. + *

+ * Adding an element beyond the maxSize will result in the oldest item being + * discarded. + */ + public void add(Entry entry) { + history.remove(entry); + history.addFirst(entry); + discardOldEntries(); + isDirty = true; + } + + public void clear() { + history.clear(); + isDirty = true; + } + + /** + * Throw away old entries until our size obeys the maxSize constraint. + */ + private void discardOldEntries() { + while (size() > getMaxSize()) { + history.removeLast(); + } + } + + /** + * Return the last added element. + */ + public Entry getLast() { + // elements in backing collection are actually in inverse order + return history.getFirst(); + } + + /** + * @return The number of items in the history is limited to. + */ + public int getMaxSize() { + return maxSize; + } + + /** + * Retrieve a List of most recent entries, (most recent first). + * @param limit Return at most this many elements. + */ + public List getRecentValid(int limit) { + ArrayList result = new ArrayList(limit); + for (Entry entry : validEntries()) { + if (limit-- <= 0) { + return result; + } + result.add(entry); + } + return result; + } + + private IPath getSavePath() { + return CorePlugin.getDefault().getStateLocation().append(historyId + HISTORY_FILE_NAME); + } + + /** + * Did this history get changed since it was last saved? + */ + public boolean isDirty() { + return isDirty; + } + + public boolean isEmpty() { + return history.isEmpty(); + } + + /** + * Iterator that starts from the most recently added element. + */ + public Iterator iterator() { + return history.iterator(); + } + + /** + * Load history contents from a file. If the file does not exist we silently + * assume the history should be empty. + *

+ * If there are errors loading file we keep the current history and log an + * exception. + */ + public void load(File file) { + try { + if (file.exists()) { + FileInputStream fIn = new FileInputStream(file); + ObjectInputStream oIn = null; + try { + oIn = new ObjectInputStream(fIn); + load(oIn); + isDirty = false; + } + finally { + if (oIn != null) { + oIn.close(); + } + else if (fIn != null) { + fIn.close(); + } + } + } + } + catch (Exception e) { + CorePlugin.log("Could not restore the command history", e); + if (file.exists()) { + // File is corrupt... + file.delete(); + } + } + } + + private void load(IPath savePath) { + load(savePath.toFile()); + } + + /** + * Load the state from an InputOutputStream, let someone else worry where + * this stream came from and how to handle error / exception making sure the + * stream is closed no matter what. + * @throws ClassNotFoundException + * @throws IOException + */ + private void load(ObjectInputStream in) throws IOException, ClassNotFoundException { + int newMaxSize = in.readInt(); + Entry[] elements = new Entry[in.readInt()]; + for (int i = 0; i < elements.length; i++) { + elements[i] = (Entry) in.readObject(); + } + + // We could do this in one go, but it is slightly nicer not to modify + // the state of this history object until we are successful reading + // all the elements. + + maxSize = newMaxSize; + history = new LinkedList(); + for (Entry element : elements) { + history.add(element); + } + isDirty = false; + } + + /** + * Save this history to a file + * @throws IOException + */ + public void save(File file) throws IOException { + if (isDirty()) { + FileOutputStream fOut = new FileOutputStream(file); + ObjectOutputStream oOut = null; + try { + oOut = new ObjectOutputStream(fOut); + save(oOut); + isDirty = false; + } + finally { + if (oOut != null) { + oOut.close(); + } + else if (fOut != null) { + fOut.close(); + } + } + } + } + + /** + * Save this history to a file + * @throws IOException + */ + private void save(IPath file) throws IOException { + save(file.toFile()); + } + + /** + * Save the state to an ObjectOutputStream, let someone else worry where + * this stream came from and how to handle error / exception making sure the + * stream is closed no matter what. + */ + private void save(ObjectOutputStream out) throws IOException { + out.writeInt(maxSize); + out.writeInt(size()); + for (Entry entry : history) { + out.writeObject(entry); + } + } + + /** + * Set the maxSize value. This will limit the number of items that will be + * retained in the history. When the limit is exceeded, older items are + * discarded. + */ + public void setMaxSize(int max) { + Assert.isLegal(max > 0); + this.maxSize = max; + discardOldEntries(); + isDirty = true; + } + + public int size() { + return history.size(); + } + + /** + * Returns an array of the elements in the history, with the newest item at + * position 0. + */ + public Entry[] toArray() { + Entry[] result = new Entry[size()]; + int i = 0; + for (Entry entry : this) { + result[i++] = entry; + } + return result; + } + + /** + * @return an Iterable (suitable for "foreach" iteration) providing only + * Entry's who's projects are valid open projects in the workspace. + */ + public Iterable validEntries() { + return new ValidProjectFilter(this, natureId); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/commandhistory/ValidProjectFilter.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/commandhistory/ValidProjectFilter.java new file mode 100644 index 000000000..a1f91e13e --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/commandhistory/ValidProjectFilter.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.internal.core.commandhistory; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.apache.commons.collections.Predicate; +import org.apache.commons.collections.iterators.FilterIterator; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.springsource.ide.eclipse.commons.core.Entry; +import org.springsource.ide.eclipse.commons.core.SpringCoreUtils; + + +/** + * Utility class, provides a method to decide whether a given command history + * Entry belongs to a project with the given nature. + *

+ * The CommandHistory stores history items that may no longer be valid (because + * the associated project has been closed/renamed/deleted etc. + *

+ * A ValidProjectFilter can be applied to an Iterable (which includes + * CommandHistory) to only yield valid entries according to the current state of + * the workspace. + *

+ * The Filtering is efficient in the sense that it does not create an extra + * collection containing the valid elements but only an iterator that skips over + * invalid entries in the original collection. + * @author Andrew Eisenberg + * @author Christian Dupuis + * @author Kris De Volder + * @since 2.5.0 + */ +public class ValidProjectFilter implements Iterable { + + private final Iterable target; + + private final String natureId; + + public ValidProjectFilter(Iterable target, String natureId) { + this.target = target; + this.natureId = natureId; + } + + @SuppressWarnings("unchecked") + public Iterator iterator() { + final Set validProjects = new HashSet(); + IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); + for (IProject project : projects) { + // Test if the selected project has the given nature + if (project.isAccessible() && SpringCoreUtils.hasNature(project, natureId)) { + validProjects.add(project.getName()); + } + } + return new FilterIterator(target.iterator(), new Predicate() { + public boolean evaluate(Object _entry) { + Entry entry = (Entry) _entry; + return validProjects.contains(entry.getProject()); + } + }); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/net/HttpClientTransportService.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/net/HttpClientTransportService.java new file mode 100644 index 000000000..74d1e249b --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/net/HttpClientTransportService.java @@ -0,0 +1,233 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.internal.core.net; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.commons.httpclient.Header; +import org.apache.commons.httpclient.HostConfiguration; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.HeadMethod; +import org.apache.commons.httpclient.util.DateParseException; +import org.apache.commons.httpclient.util.DateUtil; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.mylyn.commons.net.WebLocation; +import org.eclipse.osgi.util.NLS; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; + +/** + * A utility for accessing web resources. + * @author Steffen Pingel + */ +public class HttpClientTransportService implements ITransportService { + + private static final int BUFFER_SIZE = 4 * 1024; + + public HttpClientTransportService() { + } + + /** + * Download an HTTP-based resource + * + * @param target the target file to which the content is saved + * @param location the web location of the content + * @param monitor the monitor + * @throws IOException if a network or IO problem occurs + */ + public void download(java.net.URI uri, OutputStream out, IProgressMonitor progressMonitor) throws CoreException { + WebLocation location = new WebLocation(uri.toString()); + SubMonitor monitor = SubMonitor.convert(progressMonitor); + monitor.subTask(NLS.bind("Fetching {0}", location.getUrl())); + try { + HttpClient client = new HttpClient(); + org.eclipse.mylyn.commons.net.WebUtil.configureHttpClient(client, ""); //$NON-NLS-1$ + + GetMethod method = new GetMethod(location.getUrl()); + try { + HostConfiguration hostConfiguration = org.eclipse.mylyn.commons.net.WebUtil.createHostConfiguration( + client, location, monitor); + int result = org.eclipse.mylyn.commons.net.WebUtil.execute(client, hostConfiguration, method, monitor); + if (result == HttpStatus.SC_OK) { + long total = method.getResponseContentLength(); + if (total != -1) { + monitor.setWorkRemaining((int) total); + } + InputStream in = org.eclipse.mylyn.commons.net.WebUtil.getResponseBodyAsStream(method, monitor); + try { + in = new BufferedInputStream(in); + byte[] buffer = new byte[BUFFER_SIZE]; + int len; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + if (total != -1) { + monitor.worked(len); + } + else { + monitor.worked(1); + monitor.setWorkRemaining(10000); + } + + if (monitor.isCanceled()) { + // this point is reached if the user requests a + // cancellation + throw new OperationCanceledException(); + } + + } + } + catch (OperationCanceledException e) { + // this point is reached if there is some problem with + // the download + throw toException(location, result); + } + catch (IOException e) { + // this point is reached if there is some problem with + // the network + throw toException(location, 500); + } + finally { + in.close(); + } + } + else { + throw toException(location, result); + } + } + finally { + method.releaseConnection(); + } + } + catch (IOException e) { + throw toException(location, e); + } + finally { + monitor.done(); + } + } + + /** + * Verify availability of resources at the given web locations. Normally + * this would be done using an HTTP HEAD. + * + * @param locations the locations of the resource to verify + * @param one indicate if only one of the resources must exist + * @param progressMonitor the monitor + * @return true if the resource exists + */ + public long getLastModified(java.net.URI uri, IProgressMonitor progressMonitor) throws CoreException { + WebLocation location = new WebLocation(uri.toString()); + SubMonitor monitor = SubMonitor.convert(progressMonitor); + monitor.subTask(NLS.bind("Fetching {0}", location.getUrl())); + try { + HttpClient client = new HttpClient(); + org.eclipse.mylyn.commons.net.WebUtil.configureHttpClient(client, ""); //$NON-NLS-1$ + + HeadMethod method = new HeadMethod(location.getUrl()); + try { + HostConfiguration hostConfiguration = org.eclipse.mylyn.commons.net.WebUtil.createHostConfiguration( + client, location, monitor); + int result = org.eclipse.mylyn.commons.net.WebUtil.execute(client, hostConfiguration, method, monitor); + if (result == HttpStatus.SC_OK) { + Header lastModified = method.getResponseHeader("Last-Modified"); //$NON-NLS-1$ + if (lastModified != null) { + try { + return DateUtil.parseDate(lastModified.getValue()).getTime(); + } + catch (DateParseException e) { + // fall through + } + } + return 0; + } + else { + throw toException(location, result); + } + } + catch (IOException e) { + throw toException(location, e); + } + finally { + method.releaseConnection(); + } + } + finally { + monitor.done(); + } + } + + /** + * Read a web-based resource at the specified location using the given + * processor. + * + * @param location the web location of the content + * @param processor the processor that will handle content + * @param progressMonitor the monitor + * @throws IOException if a network or IO problem occurs + */ + public InputStream stream(java.net.URI uri, IProgressMonitor progressMonitor) throws CoreException { + WebLocation location = new WebLocation(uri.toString()); + SubMonitor monitor = SubMonitor.convert(progressMonitor); + monitor.subTask(NLS.bind("Fetching {0}", location.getUrl())); + try { + HttpClient client = new HttpClient(); + org.eclipse.mylyn.commons.net.WebUtil.configureHttpClient(client, ""); //$NON-NLS-1$ + + boolean success = false; + GetMethod method = new GetMethod(location.getUrl()); + try { + HostConfiguration hostConfiguration = org.eclipse.mylyn.commons.net.WebUtil.createHostConfiguration( + client, location, monitor); + int result = org.eclipse.mylyn.commons.net.WebUtil.execute(client, hostConfiguration, method, monitor); + if (result == HttpStatus.SC_OK) { + InputStream in = org.eclipse.mylyn.commons.net.WebUtil.getResponseBodyAsStream(method, monitor); + success = true; + return in; + } + else { + throw toException(location, result); + } + } + catch (IOException e) { + throw toException(location, e); + } + finally { + if (!success) { + method.releaseConnection(); + } + } + } + finally { + monitor.done(); + } + } + + private CoreException toException(WebLocation location, int result) { + return new CoreException(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, NLS.bind( + "Download of {0} failed: Unexpected HTTP response {1}", location.getUrl(), result))); + } + + private CoreException toException(WebLocation location, IOException e) throws CoreException { + String message = e.getMessage() != null ? e.getMessage() : "Unexpected error"; + return new CoreException(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, NLS.bind( + "Download of {0} failed: {1}", location.getUrl(), message), e)); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/net/ITransportService.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/net/ITransportService.java new file mode 100644 index 000000000..26794651a --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/net/ITransportService.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.internal.core.net; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * @author Steffen Pingel + */ +public interface ITransportService { + + public abstract void download(URI uri, OutputStream out, IProgressMonitor monitor) throws CoreException; + + public abstract long getLastModified(URI location, IProgressMonitor monitor) throws CoreException; + + public abstract InputStream stream(URI uri, IProgressMonitor monitor) throws CoreException; + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/net/JDKTransportService.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/net/JDKTransportService.java new file mode 100644 index 000000000..837cb5ca8 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/net/JDKTransportService.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.internal.core.net; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.osgi.util.NLS; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; + + +/** + * @author Terry Denney + */ +public class JDKTransportService implements ITransportService { + + private static final int BUFFER_SIZE = 4 * 1024; + + public void download(URI uri, OutputStream out, IProgressMonitor progressMonitor) throws CoreException { + SubMonitor monitor = SubMonitor.convert(progressMonitor); + try { + URL url = uri.toURL(); + + monitor.subTask(NLS.bind("Fetching {0}", url)); + + try { + InputStream in = url.openStream(); + InputStream bufferedIn = new BufferedInputStream(in); + try { + byte[] buffer = new byte[BUFFER_SIZE]; + int len; + while ((len = bufferedIn.read(buffer)) != -1) { + out.write(buffer, 0, len); + + monitor.worked(1); + monitor.setWorkRemaining(10000); + + if (monitor.isCanceled()) { + throw new OperationCanceledException(); + } + } + } + finally { + bufferedIn.close(); + in.close(); + } + } + catch (IOException e) { + throw toException(url, e); + } + } + catch (MalformedURLException e) { + throw toException(uri, e); + } + } + + public long getLastModified(URI location, IProgressMonitor monitor) throws CoreException { + try { + URL url = location.toURL(); + try { + return url.openConnection().getLastModified(); + } + catch (IOException e) { + throw toException(url, e); + } + } + catch (MalformedURLException e) { + throw toException(location, e); + } + } + + public InputStream stream(URI uri, IProgressMonitor monitor) throws CoreException { + try { + URL url = uri.toURL(); + + try { + InputStream in = url.openStream(); + return in; + } + catch (IOException e) { + throw toException(url, e); + } + } + catch (MalformedURLException e) { + throw toException(uri, e); + } + } + + private CoreException toException(URI uri, IOException e) throws CoreException { + String message = e.getMessage() != null ? e.getMessage() : "Unexpected error"; + return new CoreException(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, NLS.bind( + "Download of {0} failed: {1}", uri.getFragment(), message), e)); + } + + private CoreException toException(URL url, IOException e) throws CoreException { + String message = e.getMessage() != null ? e.getMessage() : "Unexpected error"; + return new CoreException(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, NLS.bind( + "Download of {0} failed: {1}", url, message), e)); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/net/P2TransportService.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/net/P2TransportService.java new file mode 100644 index 000000000..df9e501b9 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.core/src/org/springsource/ide/eclipse/commons/internal/core/net/P2TransportService.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.internal.core.net; + +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URI; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.springsource.ide.eclipse.commons.internal.core.CorePlugin; + +/** + * @author Steffen Pingel + */ +public class P2TransportService implements ITransportService { + + private Object transport; + + private Method downloadMethod; + + private Method streamMethod; + + private Method getLastModifiedMethod; + + public P2TransportService() throws ClassNotFoundException { + // TODO e3.5 remove reflection + try { + Class clazz; + try { + clazz = Class.forName("org.eclipse.equinox.internal.p2.repository.RepositoryTransport"); //$NON-NLS-1$ + Method getInstanceMethod = clazz.getDeclaredMethod("getInstance"); //$NON-NLS-1$ + transport = getInstanceMethod.invoke(null); + } + catch (ClassNotFoundException e) { + // the class moved to a different bundle in 3.7 + transport = getTransport_e3_7(); + clazz = transport.getClass(); + } + downloadMethod = clazz.getDeclaredMethod("download", URI.class, OutputStream.class, IProgressMonitor.class); //$NON-NLS-1$ + streamMethod = clazz.getDeclaredMethod("stream", URI.class, IProgressMonitor.class); //$NON-NLS-1$ + getLastModifiedMethod = clazz.getDeclaredMethod("getLastModified", URI.class, IProgressMonitor.class); //$NON-NLS-1$ + } + catch (LinkageError e) { + throw new ClassNotFoundException("Failed to load P2 transport", e); //$NON-NLS-1$ + } + catch (Exception e) { + throw new ClassNotFoundException("Failed to load P2 transport", e); //$NON-NLS-1$ + } + } + + private static Object getTransport_e3_7() throws Exception { + // This line is here merely to make sure that the bundle gets activated + // before trying to use the service (to get rid of a race condition). + Class clazz = Class.forName("org.eclipse.equinox.p2.core.IProvisioningAgent"); //$NON-NLS-1$ + + BundleContext bundleContext = CorePlugin.getDefault().getBundle().getBundleContext(); + ServiceReference serviceReference = bundleContext + .getServiceReference("org.eclipse.equinox.p2.core.IProvisioningAgent"); + if (serviceReference != null) { + try { + Object agent = bundleContext.getService(serviceReference); + if (agent != null) { + Method getServiceMethod = agent.getClass().getDeclaredMethod("getService", String.class); //$NON-NLS-1$ + return getServiceMethod.invoke(agent, "org.eclipse.equinox.internal.p2.repository.Transport"); + } + } + finally { + bundleContext.ungetService(serviceReference); + } + } + throw new RuntimeException("Transport service not available"); + } + + private void convertException(InvocationTargetException e) throws CoreException { + if (e.getCause() instanceof CoreException) { + throw (CoreException) e.getCause(); + } + else { + throw new CoreException(new Status(IStatus.ERROR, CorePlugin.PLUGIN_ID, e.getCause().getMessage(), + e.getCause())); + } + } + + public void download(URI uri, OutputStream out, IProgressMonitor monitor) throws CoreException { + try { + IStatus result = (IStatus) downloadMethod.invoke(transport, uri, out, monitor); + if (result.getSeverity() == IStatus.CANCEL) { + throw new OperationCanceledException(); + } + if (!result.isOK()) { + throw new CoreException(result); + } + } + catch (InvocationTargetException e) { + if (e.getCause() instanceof CoreException) { + throw (CoreException) e.getCause(); + } + } + catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public long getLastModified(URI location, IProgressMonitor monitor) throws CoreException { + try { + return (Long) getLastModifiedMethod.invoke(transport, location, monitor); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + catch (InvocationTargetException e) { + convertException(e); + } + // should never happen + throw new IllegalStateException(); + } + + public InputStream stream(URI uri, IProgressMonitor monitor) throws CoreException { + try { + return (InputStream) streamMethod.invoke(transport, uri, monitor); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + catch (InvocationTargetException e) { + convertException(e); + } + // should never happen + throw new IllegalStateException(); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/.classpath b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/.classpath new file mode 100644 index 000000000..eca7bdba8 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/.project b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/.project new file mode 100644 index 000000000..36d9ec463 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/.project @@ -0,0 +1,28 @@ + + + org.springsource.ide.eclipse.commons.frameworks.core + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/.settings/org.eclipse.jdt.core.prefs b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..3a2153707 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/META-INF/MANIFEST.MF b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/META-INF/MANIFEST.MF new file mode 100644 index 000000000..981975bb5 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/META-INF/MANIFEST.MF @@ -0,0 +1,39 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-SymbolicName: org.springsource.ide.eclipse.commons.frameworks.core;singleton:=true +Bundle-Version: 4.8.1.qualifier +Bundle-Activator: org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator +Bundle-Vendor: %providerName +Require-Bundle: org.eclipse.ui, + org.eclipse.jdt.core, + org.eclipse.core.resources, + org.eclipse.jdt.debug.ui, + org.apache.commons.io, + org.apache.commons.codec, + org.springsource.ide.eclipse.commons.livexp, + io.projectreactor.reactor-core;bundle-version="[3.3.1,3.3.2)", + org.reactivestreams.reactive-streams, + org.eclipse.jface.text +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Eclipse-LazyStart: true +Bundle-ActivationPolicy: lazy +Export-Package: org.springsource.ide.eclipse.commons.frameworks.core, + org.springsource.ide.eclipse.commons.frameworks.core.async, + org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager, + org.springsource.ide.eclipse.commons.frameworks.core.internal.cache, + org.springsource.ide.eclipse.commons.frameworks.core.internal.commands, + org.springsource.ide.eclipse.commons.frameworks.core.internal.java, + org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins, + org.springsource.ide.eclipse.commons.frameworks.core.legacyconversion, + org.springsource.ide.eclipse.commons.frameworks.core.maintype, + org.springsource.ide.eclipse.commons.frameworks.core.util, + org.springsource.ide.eclipse.commons.frameworks.core.workspace +Bundle-Localization: plugin +Import-Package: org.eclipse.core.internal.runtime, + org.eclipse.core.runtime, + org.eclipse.core.runtime.jobs, + org.eclipse.core.runtime.preferences, + org.osgi.framework, + org.osgi.framework.wiring, + org.osgi.service.prefs diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/about.html b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/about.html new file mode 100644 index 000000000..ae88621cc --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/about.html @@ -0,0 +1,22 @@ + + + + +About Eclipse Integration Commons + + + +

About Eclipse Integration Commons

+

June 30, 2015

+

Abstract

+

The Eclipse Integration Commons project provides components that help +other projects to implement Eclipse-based tooling.

+

License

+

Pivotal Software, Inc. (“Pivotal”) makes available all content +in this download ("Content"). Unless otherwise indicated below, the +Content is provided to you under the terms and conditions of the Eclipse +Public License Version 1.0 ("EPL"). A copy of the EPL is available in +the file called license.txt. For purposes of the EPL, "Program" will +mean the Content.

+ + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/build.properties b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/build.properties new file mode 100644 index 000000000..ebe02751c --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/build.properties @@ -0,0 +1,6 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + plugin.properties,\ + about.html diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/plugin.properties b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/plugin.properties new file mode 100644 index 000000000..baaecf126 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/plugin.properties @@ -0,0 +1,2 @@ +pluginName=SpringSource Tool Suite Framework Support Core +providerName=Pivotal Software, Inc. diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/pom.xml b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/pom.xml new file mode 100644 index 000000000..16180fb70 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + org.springframework.boot.ide + org.springframework.boot.ide.servers + 4.8.1-SNAPSHOT + ../pom.xml + + + org.springsource.ide.eclipse.commons.frameworks.core + eclipse-plugin + org.springsource.ide.eclipse.commons.frameworks.core + + + + + org.eclipse.tycho + tycho-p2-plugin + ${tycho-version} + + + second-generate-p2-metadata + + p2-metadata + + verify + + + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/readme-dependencies.txt b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/readme-dependencies.txt new file mode 100644 index 000000000..6eb59fed3 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/readme-dependencies.txt @@ -0,0 +1,12 @@ +Important notes about dependencies for commons.frameworks.core + +commons.FRAMEWORKS.core is dependended on by other framework tooling like GGTS, CloudFoundry tooling etc. +These tooling do not want to drag in dependencies on things like mylin, m2e etc. Generally only +dependencies on 'plain Eclipse' are allowed in commons fw core. + +Since commons.core has dependencies on mylin, m2e etc. commons.frameworks.core should also *not* +dependend on commons.core. + +However it is ok for commons.core (or really any other module) to depend on commons.fw.core. +commons.fw.core should be a 'leaf' in the dependency tree if ignore dependencies that are +commonly available in plain eclipse. \ No newline at end of file diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/ExceptionUtil.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/ExceptionUtil.java new file mode 100644 index 000000000..584900713 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/ExceptionUtil.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; + +/** + * Utility methods to convert exceptions into other types of exceptions, status + * objects etc. + * + * Deprecated because this class was moved to livexp bundle. Use the moved class instead of this one. + * + * @author Kris De Volder + */ +@Deprecated +public class ExceptionUtil { + + public static CoreException coreException(int severity, String msg) { + return coreException(status(severity, msg)); + } + + public static CoreException coreException(IStatus status) { + if (status==null) { + return coreException("Null status?"); + } + Throwable e = status.getException(); + if (e == null) { + return new CoreException(status); + } + else if (e instanceof CoreException) { + return (CoreException) e; + } + return new CoreException(status); + } + + public static CoreException coreException(String msg) { + return coreException(IStatus.ERROR, msg); + } + + public static CoreException coreException(Throwable e) { + if (e instanceof CoreException) { + return (CoreException) e; + } + else { + return coreException(status(e)); + } + } + + public static Throwable getDeepestCause(Throwable e) { + Throwable cause = e; + Throwable parent = e.getCause(); + while (parent != null && parent != e) { + cause = parent; + parent = cause.getCause(); + } + return cause; + } + + public static String getMessage(Throwable e) { + // The message of nested exception is usually more interesting than the + // one on top. + Throwable cause = getDeepestCause(e); + String msg = cause.getClass().getSimpleName() + ": " + cause.getMessage(); + return msg; + } + + public static IllegalStateException notImplemented(String string) { + return new IllegalStateException("Not implemented: " + string); + } + + public static IStatus status(int severity, String msg) { + return new Status(severity, FrameworkCoreActivator.PLUGIN_ID, msg); + } + + public static IStatus status(Throwable e) { + return status(IStatus.ERROR, e); + } + + public static IStatus status(int severity, Throwable e) { + if (e instanceof OperationCanceledException || e instanceof InterruptedException) { + return Status.CANCEL_STATUS; + } + if (e instanceof CoreException) { + IStatus status = ((CoreException) e).getStatus(); + if (status != null && status.getSeverity() == severity) { + Throwable ee = status.getException(); + if (ee != null) { + return status; + } + } + } + return new Status(severity, FrameworkCoreActivator.PLUGIN_ID, getMessage(e), e); + } + + public static IStatus status(String msg) { + return status(IStatus.ERROR, msg); + } + + public static final IStatus OK_STATUS = status(IStatus.OK, ""); + + public static Exception exception(Throwable e) { + if (e instanceof Exception) { + return (Exception)e; + } else { + return coreException(e); + } + } + + public static RuntimeException unchecked(Exception e) { + return new RuntimeException(e); + } + + public static String stacktrace() { + return stacktrace(new Exception("Stacktrace")); + } + + public static String stacktrace(Exception exception) { + ByteArrayOutputStream dump = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(dump); + try { + exception.printStackTrace(out); + } finally { + out.close(); + } + return dump.toString(); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/FrameworkCoreActivator.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/FrameworkCoreActivator.java new file mode 100644 index 000000000..3219ff387 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/FrameworkCoreActivator.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core; + +import java.net.URISyntaxException; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; +import org.springsource.ide.eclipse.commons.frameworks.core.legacyconversion.IConversionConstants; + +/** + * @author Nieraj Singh + * @author Christian Dupuis + * @since 2.5.0 + */ +public class FrameworkCoreActivator extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.springsource.ide.eclipse.commons.frameworks.core"; //$NON-NLS-1$ + + // The shared instance + private static FrameworkCoreActivator plugin; + + /** + * The constructor + */ + public FrameworkCoreActivator() { + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext + * ) + */ + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + } + + /* + * (non-Javadoc) + * + * @see + * org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext + * ) + */ + public void stop(BundleContext context) throws Exception { + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static FrameworkCoreActivator getDefault() { + return plugin; + } + + public static void logError(String message, Throwable exception) { + getDefault().getLog().log(createErrorStatus(message, exception)); + } + public static void log(Throwable e) { + getDefault().getLog().log(createErrorStatus(e.getMessage(), e)); + } + + public static IStatus createErrorStatus(String message, Throwable exception) { + if (message == null) { + message = ""; + } + return new Status(IStatus.ERROR, PLUGIN_ID, 0, message, exception); + } + + public static IStatus createWarningStatus(String message, + Throwable exception) { + if (message == null) { + message = ""; + } + return new Status(IStatus.WARNING, PLUGIN_ID, 0, message, exception); + } + + /** + * Returns true if the plugin id has not yet run legacy conversion for this workspace yet + * @param pluginid plugin id to check + * @return true iff legacy conversion has not taken place for this plugin, but it has for the workspace as a whole + */ + public boolean shouldMigratePlugin(String pluginid) { + if (getPreferenceStore().getBoolean(IConversionConstants.LEGACY_MIGRATION_ALREADY_DONE)) { + String plugins = getPreferenceStore().getString(IConversionConstants.LEGACY_MIGRATION_PLUGINS); + return plugins.contains("," + pluginid + ","); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + // workspace legacy migration has not yet been successfully performed + return false; + } + } + + public void registerPluginMigrationComplete(String pluginid) { + String plugins = getPreferenceStore().getString(IConversionConstants.LEGACY_MIGRATION_PLUGINS); + plugins += "," + pluginid + ","; //$NON-NLS-1$ //$NON-NLS-2$ + getPreferenceStore().putValue(IConversionConstants.LEGACY_MIGRATION_PLUGINS, plugins); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/CachingValueProvider.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/CachingValueProvider.java new file mode 100644 index 000000000..7ade6b990 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/CachingValueProvider.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * Copyright (c) 2016, 2019 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.async; + +import java.time.Duration; +import java.util.Collection; + +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.springsource.ide.eclipse.commons.frameworks.core.internal.cache.Cache; +import org.springsource.ide.eclipse.commons.frameworks.core.internal.cache.LimitedTimeCache; + +import reactor.core.publisher.Flux; + +/** + * COPIED FROM: + * org.springframework.ide.eclipse.boot.properties.editor.metadata.CachingValueProvider + *

+ * TODO: refactor so that there is only one CachingValueProvider that can be reused + *

+ * A abstract {@link ValueProviderStrategy} that is mean to help speedup successive invocations of + * content assist with a similar 'query' string. + *

+ * This implementation is meant to be used for providers that use potentially lengthy/expensive searches + * to determine hints. Since content assist hints are requested by Eclipse CA framework directly on + * the UI thread, they can not simply perform a lengthy search and block UI thread until it finished. + *

+ * This implementation therefore does the following: + *

    + *
  • Limit the duration of time spent on the UI thread. + *
  • Cache results of searches for a limited time. + *
  • Speedup queries for successive queries by using the already cached result of a similar (prefix) query. + *
  • When the time spent on UI thread waiting for a current search exceeds the allowed time limit, + * return immediately with whatever results have been found so far. + *
+ * + * TODO: rather than an abstract class this should really be 'Wrapper' class that delegates to another + * {@link ValueProviderStrategy} and adds a cache in front of it. + * + * @author Kris De Volder + */ +public abstract class CachingValueProvider { + +// protected static final boolean DEBUG = (""+Platform.getLocation()).contains("kdvolder"); +// +// protected static void debug(String string) { +// if (DEBUG) { +// System.out.println(string); +// } +// } + + private static final Duration DEFAULT_TIMEOUT = Duration.ofMillis(1000); + + /** + * Content assist is called inside UI thread and so doing something lenghty things + * like a JavaSearch will block the UI thread completely freezing the UI. So, we + * only return as many results as can be obtained within this hard TIMEOUT limit. + */ + public static Duration TIMEOUT = DEFAULT_TIMEOUT; + + /** + * The maximum number of results returned for a single request. Used to limit the + * values that are cached per entry. + */ + private int MAX_RESULTS = 500; + + private Cache cache = createCache(); + + private class CacheEntry { + boolean isComplete = false; + int count = 0; + Flux values; + + public CacheEntry(String query, Flux producer) { + values = producer + .doOnNext((e) -> { + count++; +// debug("onNext["+query+":"+count+"]: "+e.getValue().toString()); + }) + .doOnComplete(() -> { +// debug("onComplete["+query+":"+count+"]"); + isComplete = true; + }) + .take(MAX_RESULTS) + .cache(MAX_RESULTS); + values.subscribe(); // create infinite demand so that we actually force cache entries to be fetched upto the max. + } + + @Override + public String toString() { + return "CacheEntry [isComplete=" + isComplete + ", count=" + count + "]"; + } + + } + + + public final Flux getValues(IType type, IJavaSearchScope scope) { +// debug("CA query: "+query); + String key = key(type); + CacheEntry cached = cache.get(key); + if (cached==null) { + cache.put(key, cached = new CacheEntry(key, getValuesIncremental(type, scope))); + } + return cached.values; + } + + public final Collection getValuesNow(IType type, IJavaSearchScope scope) { + return this.getValues(type, scope) + .take(CachingValueProvider.TIMEOUT) + .collectList() + .block(); + } + + /** + * Tries to use an already cached, complete result for a query that is a prefix of the current query to speed things up. + *

+ * Falls back on doing a full-blown search if there's no usable 'prefix-query' in the cache. + */ + private Flux getValuesIncremental(IType expectedType, IJavaSearchScope scope) { +// debug("trying to solve "+query+" incrementally"); + CacheEntry cached = cache.get(key(expectedType)); + if (cached!=null) { + System.out.println("cached "+expectedType+": "+cached); + if (cached.isComplete) { +// debug("filtering "+subquery+" -> "+query); + return cached.values +// .doOnNext((hint) -> debug("filter["+query+"]: "+hint.getValue())) +// .filter((hint) -> 0!=FuzzyMatcher.matchScore(query, hint.getValue().toString())) + ; + } else { +// debug("subquery "+subquery+" cached but is incomplete"); + } + } +// debug("full search for: "+query); + return getValuesAsycn(expectedType, scope); + } + + protected abstract Flux getValuesAsycn(IType expectedType, IJavaSearchScope scope); + + private String key(IType expectedType) { + return expectedType == null ? null : expectedType.getFullyQualifiedName(); + } + + protected Cache createCache() { + return new LimitedTimeCache<>(Duration.ofMinutes(1)); + } + + public static void restoreDefaults() { + TIMEOUT = DEFAULT_TIMEOUT; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/ConstructorSearchValueProvider.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/ConstructorSearchValueProvider.java new file mode 100644 index 000000000..79da4619c --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/ConstructorSearchValueProvider.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2016, 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.async; + +import java.util.function.Function; + +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchPattern; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * Searches for all constructors for a given type. + */ +public class ConstructorSearchValueProvider extends CachingValueProvider { + + @Override + public Flux getValuesAsycn(IType type, IJavaSearchScope scope) { + try { + return new FluxConstructorSearch() + .scope(scope) + .pattern("*") + .patternRule(SearchPattern.R_PATTERN_MATCH) + .search() + .flatMap(getPostProcessor()); + } catch (Exception e) { + return Flux.error(e); + } + } + + protected Function> getPostProcessor() { + return (match) -> { + return Mono.justOrEmpty(match); + }; + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/FluxConstructorSearch.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/FluxConstructorSearch.java new file mode 100644 index 000000000..99ee2dc31 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/FluxConstructorSearch.java @@ -0,0 +1,186 @@ +/******************************************************************************* + * Copyright (c) 2016, 2018 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.async; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.SearchRequestor; +import org.eclipse.jdt.internal.compiler.env.AccessRestriction; +import org.eclipse.jdt.internal.core.search.BasicSearchEngine; +import org.eclipse.jdt.internal.core.search.IRestrictedAccessConstructorRequestor; +import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.ReplayProcessor; +import reactor.util.concurrent.Queues; + +/** + * + * NOTE: + * This is derived from: + * org.springsource.ide.eclipse.commons.frameworks.core.async.FluxJdtSearch + *

+ * TODO: + * There should only one reusable Flux JDT Search. Refactor so that FluxJdtSearch and FluxConstructorSearch + * share a common base + *

+ * Helper class to perform a search using Eclipse JDT search engine returning + * the search results as a Flux. + *

+ * The conversion from Eclipse callback style using {@link SearchRequestor} involves + * a buffer that allows subscribers to attach to the Flux after the search has already + * started without loosing results. However, if the buffer overflows then results will + * be lost. + *

+ * Clients should therfore start consuming the results as soon as possible and avoid + * blocking the pipeline to avoid the loss of results they may care about. + *

+ * Alternatively, client can specify a large enough buffer size so that the buffer can hold + * at least as many results as the client may care to retrieve. This will allow the returned + * Flux to be reused any number of times without timing constraints, provided that the + * consumer never requests more than the number of buffered results. + * + * @author Kris De Volder + */ +@SuppressWarnings("restriction") +public class FluxConstructorSearch { + + private static final boolean DEBUG = (""+Platform.getLocation()).contains("kdvolder"); + + private static void debug(String string) { + if (DEBUG) { + System.out.println(string); + } + } + + private BasicSearchEngine engine = new BasicSearchEngine(); + private IJavaSearchScope scope = SearchEngine.createWorkspaceScope(); + private String pattern = null; + private int patternRule = SearchPattern.R_PATTERN_MATCH; + private int bufferSize = Queues.SMALL_BUFFER_SIZE; + private boolean useSystemJob = false; + private int jobPriority = Job.INTERACTIVE; + + public FluxConstructorSearch engine(BasicSearchEngine engine) { + this.engine = engine; + return this; + } + + public FluxConstructorSearch scope(IJavaSearchScope scope) { + this.scope = scope; + return this; + } + + public FluxConstructorSearch bufferSize(int bufferSize) { + this.bufferSize = bufferSize; + return this; + } + + public FluxConstructorSearch patternRule(int patternRule) { + this.patternRule = patternRule; + return this; + } + + public FluxConstructorSearch pattern(String pattern) { + this.pattern = pattern; + return this; + } + + + /** + * Implementation of {@link SearchRequestor} that emits search results to an {@link ReplayProcessor} + * with replay capability. + * + * @author Kris De Volder + */ + class FluxSearchRequestor { + + private boolean isCanceled = false; + private ReplayProcessor emitter = ReplayProcessor.create(bufferSize); + private Flux flux = emitter.doOnCancel(() -> isCanceled=true); + + public Flux asFlux() { + return flux; + } + + public void acceptSearchMatch(JavaConstructorHint match) { + if (isCanceled) { + debug("!!!! canceling search !!!!"); + //Stop searching + throw new OperationCanceledException(); + } + emitter.onNext(match); + } + + public void cancel() { + isCanceled = true; + } + + public void done() { + emitter.onComplete(); + } + } + + public Flux search() { + validate(); + if (scope==null) { + return Flux.empty(); + } + final FluxSearchRequestor requestor = new FluxSearchRequestor(); + Job job = new Job("Search for "+pattern) { + @Override + protected IStatus run(IProgressMonitor monitor) { + long start = System.currentTimeMillis(); + debug("Starting search for '"+pattern+"'"); + try { + engine.searchAllConstructorDeclarations(null, pattern.toCharArray(), patternRule, + scope, new IRestrictedAccessConstructorRequestor() { + + @Override + public void acceptConstructor(int modifiers, char[] simpleTypeName, int parameterCount, + char[] signature, char[][] parameterTypes, char[][] parameterNames, int typeModifiers, + char[] packageName, int extraFlags, String path, AccessRestriction access) { + requestor.acceptSearchMatch(JavaConstructorHint.asHint(modifiers, simpleTypeName, parameterCount, signature, parameterTypes, parameterNames, typeModifiers, packageName, extraFlags, path, access)); + } + + }, IJavaSearchConstants.FORCE_IMMEDIATE_SEARCH, monitor); + requestor.done(); + } catch (Exception e) { + debug("Canceled search for: "+pattern); + debug(" exception: "+ExceptionUtil.getMessage(e)); + long duration = System.currentTimeMillis() - start; + debug(" duration: "+duration+" ms"); + requestor.cancel(); + } + return Status.OK_STATUS; + } + }; + job.setSystem(useSystemJob); + job.setPriority(jobPriority); + job.schedule(); + return requestor.asFlux(); + } + + private void validate() { + Assert.isNotNull(engine, "engine"); + Assert.isNotNull(pattern, "pattern"); + Assert.isLegal(bufferSize > 0); + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/FluxJdtSearch.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/FluxJdtSearch.java new file mode 100644 index 000000000..a392c4ef0 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/FluxJdtSearch.java @@ -0,0 +1,229 @@ +/******************************************************************************* + * Copyright (c) 2016, 2018 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.async; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.SearchParticipant; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.SearchRequestor; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; +import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil; + +import reactor.core.publisher.Flux; +import reactor.core.publisher.ReplayProcessor; +import reactor.util.concurrent.Queues; + +/** + * Helper class to perform a search using Eclipse JDT search engine returning + * the search results as a Flux. + *

+ * The conversion from Eclipse callback style using {@link SearchRequestor} involves + * a buffer that allows subscribers to attach to the Flux after the search has already + * started without loosing results. However, if the buffer overflows then results will + * be lost. + *

+ * Clients should therfore start consuming the results as soon as possible and avoid + * blocking the pipeline to avoid the loss of results they may care about. + *

+ * Alternatively, client can specify a large enough buffer size so that the buffer can hold + * at least as many results as the client may care to retrieve. This will allow the returned + * Flux to be reused any number of times without timing constraints, provided that the + * consumer never requests more than the number of buffered results. + * + * @author Kris De Volder + */ +public class FluxJdtSearch { + + private static final boolean DEBUG = (""+Platform.getLocation()).contains("kdvolder"); + + private static void debug(String string) { + if (DEBUG) { + System.out.println(string); + } + } + + private SearchEngine engine = new SearchEngine(); + private IJavaSearchScope scope = SearchEngine.createWorkspaceScope(); + private SearchPattern pattern = null; + private SearchParticipant[] participants = new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()}; + private int bufferSize = Queues.SMALL_BUFFER_SIZE; + private boolean useSystemJob = false; + private int jobPriority = Job.INTERACTIVE; + + public FluxJdtSearch engine(SearchEngine engine) { + this.engine = engine; + return this; + } + + public FluxJdtSearch scope(IJavaSearchScope scope) { + this.scope = scope; + return this; + } + + public FluxJdtSearch scope(IProject project) throws JavaModelException { + return scope(JavaCore.create(project)); + } + + public FluxJdtSearch scope(IJavaProject project) throws JavaModelException { + return scope(searchScope(project)); + } + + public FluxJdtSearch bufferSize(int bufferSize) { + this.bufferSize = bufferSize; + return this; + } + + public FluxJdtSearch pattern(SearchPattern pattern) { + this.pattern = pattern; + return this; + } + + /** + * Create a search scope that includes a given project and its dependencies. + */ + public static IJavaSearchScope searchScope(IJavaProject javaProject, boolean includeBinaries) throws JavaModelException { + int includeMask = + IJavaSearchScope.REFERENCED_PROJECTS | + IJavaSearchScope.SOURCES; + if (includeBinaries) { + includeMask = includeMask | IJavaSearchScope.APPLICATION_LIBRARIES; + } + return SearchEngine.createJavaSearchScope(new IJavaElement[] {javaProject}, includeMask); + } + + public static IJavaSearchScope searchScope(IJavaProject javaProject) throws JavaModelException { + return searchScope(javaProject, true); + } + + + /** + * Implementation of {@link SearchRequestor} that emits search results to an {@link ReplayProcessor} + * with replay capability. + * + * @author Kris De Volder + */ + class FluxSearchRequestor extends SearchRequestor { + + private boolean isCanceled = false; + private ReplayProcessor emitter = ReplayProcessor.create(bufferSize); + private Flux flux = emitter.doOnCancel(() -> isCanceled=true); + + public Flux asFlux() { + return flux; + } + + @Override + public void acceptSearchMatch(SearchMatch match) throws CoreException { + if (isCanceled) { + debug("!!!! canceling search !!!!"); + //Stop searching + throw new OperationCanceledException(); + } + emitter.onNext(match); + } + + public void cancel() { + isCanceled = true; + } + + public void done() { + emitter.onComplete(); + } + } + + protected SearchEngine searchEngine() { + return new SearchEngine(); + } + + protected SearchParticipant[] participants() { + return new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()}; + } + + public Flux search() { + validate(); + if (scope==null) { + return Flux.empty(); + } + final FluxSearchRequestor requestor = new FluxSearchRequestor(); + Job job = new Job("Search for "+pattern) { + @Override + protected IStatus run(IProgressMonitor monitor) { + long start = System.currentTimeMillis(); + debug("Starting search for '"+pattern+"'"); + try { + searchEngine().search(pattern, participants, scope, requestor, monitor); + requestor.done(); + } catch (Exception e) { + debug("Canceled search for: "+pattern); + debug(" exception: "+ExceptionUtil.getMessage(e)); + long duration = System.currentTimeMillis() - start; + debug(" duration: "+duration+" ms"); + requestor.cancel(); + } + return Status.OK_STATUS; + } + }; + job.setSystem(useSystemJob); + job.setPriority(jobPriority); + job.schedule(); + return requestor.asFlux(); + } + + private void validate() { + Assert.isNotNull(engine, "engine"); + //We allow scope to be set to null. This means there's no valid scope (or empty scope) + // and the search should just return no results. + // Assert.isNotNull(scope, "scope"); + Assert.isNotNull(pattern, "pattern"); + Assert.isNotNull(participants, "participants"); + Assert.isLegal(bufferSize > 0); + } + + public static IJavaSearchScope workspaceScope(boolean includeBinaries) { + if (includeBinaries) { + return SearchEngine.createWorkspaceScope(); + } else { + List projects = new ArrayList<>(); + for (IProject p : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { + try { + if (p.isAccessible() && p.hasNature(JavaCore.NATURE_ID)) { + IJavaProject jp = JavaCore.create(p); + projects.add(jp); + } + } catch (Exception e) { + FrameworkCoreActivator.log(e); + } + } + int includeMask = IJavaSearchScope.SOURCES; + return SearchEngine.createJavaSearchScope(projects.toArray(new IJavaElement[projects.size()]), includeMask); + } + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/JavaConstructorHint.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/JavaConstructorHint.java new file mode 100644 index 000000000..bc2213624 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/JavaConstructorHint.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.async; + +import org.eclipse.jdt.internal.compiler.env.AccessRestriction; + +@SuppressWarnings("restriction") +/** + * Contains information required for JDT's BasicSearchEngine to search for constructors. Note that this is to support + * "legacy" implementation around an internal JDT search engine that allows for searching constructors + * + * + */ +public class JavaConstructorHint { + public int modifiers; + public char[] simpleTypeName; + public int parameterCount; + public char[] signature; + public char[][] parameterTypes; + public char[][] parameterNames; + public int typeModifiers; + public char[] packageName; + public int extraFlags; + public String path; + public AccessRestriction access; + + public JavaConstructorHint(int modifiers, char[] simpleTypeName, int parameterCount, char[] signature, + char[][] parameterTypes, char[][] parameterNames, int typeModifiers, char[] packageName, int extraFlags, + String path, AccessRestriction access) { + this.modifiers = modifiers; + this.simpleTypeName = simpleTypeName; + this.parameterCount = parameterCount; + this.signature = signature; + this.parameterTypes = parameterTypes; + this.parameterNames = parameterNames; + this.typeModifiers = typeModifiers; + this.packageName = packageName; + this.extraFlags = extraFlags; + this.path = path; + this.access = access; + } + + public static JavaConstructorHint asHint(int modifiers, char[] simpleTypeNames, int parameterCount, + char[] signatures, char[][] parameterTypes, char[][] parameterNames, int typeModifiers, char[] packageNames, + int extraFlags, String path, AccessRestriction access) { + return new JavaConstructorHint(modifiers, simpleTypeNames, parameterCount, signatures, parameterTypes, + parameterNames, typeModifiers, packageNames, extraFlags, path, access); + } +} \ No newline at end of file diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/Resolvable.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/Resolvable.java new file mode 100644 index 000000000..6e764c912 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/Resolvable.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2015 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.async; + +/** + * An object implementing this interface represents some entity awaiting + * the result of an asynchronous computation. The result should + * either be a value passed to the 'resolve' method (if the computation + * succeeded) or an Exception, passed to the 'reject' method (if the + * compuration failed). + *

+ * Generally a computation can only complete by a call to resolve or + * reject. + *

+ * Implementations of Resolvable may rely on this assumption to free up + * resources when resolve/reject are called. + *

+ * Generally, implementations of {@link Resolvable} should guard against + * bad/buggy callers and by disregarding additional calls to resolve/reject + * after the first. + * + * + * @author Kris De Volder + */ +public interface Resolvable { + void resolve(T value); + void reject(Exception e); +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/ResolvableFuture.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/ResolvableFuture.java new file mode 100644 index 000000000..cada35edd --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/async/ResolvableFuture.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2015 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.async; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * A java.util.concurrent.Future which isn't created by some + * executor, but rather just an object which is awaiting some kind + * of event to resolve or reject it. + * + * Deprecated. Now that we have Java 8 you can use {@link CompletableFuture} + * + * @author Kris De Volder + */ +@Deprecated +public class ResolvableFuture implements Future, Resolvable { + + private boolean isPending = true; + private Exception exception; + private T value; + + public synchronized void resolve(T v) { + this.value = v; + this.isPending = false; + this.notifyAll(); + } + + public synchronized void reject(Exception e) { + if (!isPending) { + this.exception = e; + this.isPending = false; + this.notifyAll(); + } + } + + @Override + public synchronized boolean cancel(boolean mayInterruptIfRunning) { + if (isPending) { + reject(new CancellationException()); + return true; + } + return false; + } + + @Override + public boolean isCancelled() { + return exception instanceof CancellationException; + } + + @Override + public synchronized boolean isDone() { + return !isPending; + } + + @Override + public synchronized T get() throws InterruptedException, ExecutionException { + while (isPending) { + wait(); + } + if (isPending) { + throw new IllegalStateException("Still pending, can't fetch value or exception"); + } + if (exception!=null) { + throw new ExecutionException(exception); + } else { + return value; + } + } + + @Override + public synchronized T get(long timeout, TimeUnit unit) throws InterruptedException, + ExecutionException, TimeoutException { + long startTime = System.currentTimeMillis(); + long endTime = startTime + unit.toMillis(timeout); + long waitTime; + while (isPending + && 0 < (waitTime = endTime - System.currentTimeMillis())) { + wait(waitTime); + } + if (isPending) { + throw new TimeoutException(); + } + if (exception!=null) { + throw new ExecutionException(exception); + } else { + return value; + } + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/DownloadManager.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/DownloadManager.java new file mode 100644 index 000000000..9ebc04e0c --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/DownloadManager.java @@ -0,0 +1,287 @@ +/******************************************************************************* + * Copyright (c) 2012, 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URISyntaxException; +import java.net.URL; + +import org.apache.commons.io.FileUtils; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.URIUtil; +import org.eclipse.swt.widgets.Display; +import org.springsource.ide.eclipse.commons.frameworks.core.ExceptionUtil; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; +import org.springsource.ide.eclipse.commons.frameworks.core.util.FileUtil; + +/** + * Manages a cache of downloaded content. + * + * @author Kris De Volder + */ +public class DownloadManager { + + /** + * An instance of this interface represent an action to execute on a downloaded + * File. The action may indicate failure by throwing an exception or by + * returning false. A failed action may trigger the DownloadManager to + * clear the cache and try again for a limited number of times. + */ + public interface DownloadRequestor { + void exec(File downloadedFile) throws Exception; + } + + /** + * An instance of this class represents some type of service able to fetch the + * content data. + */ + public interface DownloadService { + void fetch(URL url, OutputStream writeTo) throws IOException; + } + + private final File cacheDirectory; + private final DownloadService downloader; + private boolean deleteCacheOnDispose = false; + private boolean allowUIThread = false; + private int tries = 5; //5 retries by default + private long retryInterval = 0; //no wait between retries by default. + + public DownloadManager(DownloadService downloader, File cacheDir) throws IOException { + if (cacheDir==null) { + cacheDir = FileUtil.createTempDirectoryWithTimestamp("downloadCache"); + this.deleteCacheOnDispose = true; // This dir can not be used by anynone after this DLM is diposed so better delete it. + } + if (downloader==null) { + downloader = new SimpleDownloadService(); + } + this.downloader = downloader; + this.cacheDirectory = cacheDir; + if (!cacheDir.isDirectory()) { + Assert.isTrue(cacheDir.mkdirs(), "Couldn't create cache directory at "+cacheDir); + } + } + + public DownloadManager clearCache() { + FileUtils.deleteQuietly(cacheDirectory); + cacheDirectory.mkdirs(); + return this; + } + + /** + * Deprecated use one of the other contructors (probably you want to use + * the one that takes a {@link URLConnectionFactory} if you where using + * this one. + */ + @Deprecated + public DownloadManager() throws IOException { + this(new URLConnectionFactory()); + } + + /** + * Create a Default downloadmanager. This downloadmanager does not use authentication and + * simply uses standard JavaApi to fetch url content. + *

+ * A new temp directory is created to use as the cache directory. + *

+ * When this DownloadManager is disposed the cache directory is deleted (since the next + * DownloadManager created in this way will use a different cache dir anyway there isn't + * much use leaving it around). + */ + public DownloadManager(URLConnectionFactory urlConnectionFactory) throws IOException { + this(new SimpleDownloadService(urlConnectionFactory), null); + this.deleteCacheOnDispose = true; + } + + /** + * This method is deprecated, please use doWithDownload to provide proper recovery + * for cache corruption. + */ + @Deprecated + public File downloadFile(DownloadableItem item) throws URISyntaxException, FileNotFoundException, CoreException, IOException, UIThreadDownloadDisallowed { + File target = getLocalLocation(item); + if (target.exists()) { + return target; + } + + if (!allowUIThread && Display.getCurrent()!=null) { + throw new UIThreadDownloadDisallowed("Don't call download manager from the UI Thread unless the data is already cached."); + } +// +// ); + //It is important not to lock the UI thread for downloads!!! + // If the UI thread is well behaved, we assume it will be careful not to call this method unless the + // content is already cached. So once we get past the exists check it is ok to grab the lock. + synchronized (this) { + //It is possible that multiple thread where waiting to enter here... only one of them should proceed + // to actually download. So make sure to retest the target.exists() condition to avoid multiple downloads + if (target.exists()) { + return target; + } + if (!cacheDirectory.exists()) { + cacheDirectory.mkdirs(); + } + + File targetPart = new File(target.toString()+".part"); + FileOutputStream out = new FileOutputStream(targetPart); + try { + URL url = item.getURL(); + System.out.println("Downloading " + url + " to " + target); + + downloader.fetch(url, out); + } + finally { + out.close(); + } + + if (!targetPart.renameTo(target)) { + throw new IOException("Error while renaming " + targetPart + " to " + target); + } + + return target; + } + } + + /** + * Get the local location of given item. Note that the item may or may not yet be downloaded so + * there are no guarantees there is anything meaningful to be found there. + */ + public File getLocalLocation(DownloadableItem item) { + URL url = item.getURL(); + String protocol = url.getProtocol(); + try { + if ("file".equals(protocol)) { + //already local, so don't bother downloading. + return new File(URIUtil.toURI(url)); + } + } catch (URISyntaxException e) { + FrameworkCoreActivator.log(e); + } + + String filename = item.getFileName(); + + File target = new File(cacheDirectory, filename); + return target; + } + + /** + * This method tries to download or fetch a File from the cache, then passes the + * downloaded file to the DownloadRequestor. + *

+ * If the requestor fails to properly execute on the downloaded file, the cache + * will be presumed to be corrupt. The file will be deleted from the cache + * and the download will be tried again. (for a limited number of times) + */ + public void doWithDownload(DownloadableItem target, DownloadRequestor action) throws Exception { + int tries = getTries(); // try at most X times + Throwable e = null; + File downloadedFile = null; + do { + tries--; + try { + downloadedFile = downloadFile(target); + action.exec(downloadedFile); + return; // action succeeded without exceptions + } catch (UIThreadDownloadDisallowed caught) { + //No sense retrying this as it will just fail again... + throw caught; + } catch (Throwable caught) { + FrameworkCoreActivator.log(caught); + //Presume the cache may be corrupt! + //System.out.println("Delete corrupt download: "+downloadedFile); + if (downloadedFile!=null) { + downloadedFile.delete(); + downloadedFile = null; + } + e = caught; + if (tries>0 && retryInterval>0) { + Thread.sleep(retryInterval); + } + } + } while (tries>0); + //Can only get here if action failed to execute on downloaded file... + //thus, e can not be null. + throw ExceptionUtil.exception(e); + } + + /** + * @since 3.6.3 + */ + public int getTries() { + return tries; + } + + /** + * Sets the maximum number of times DownloadManager will try and retyr to fetch + * a failed download. Note that the first try also counts. So if 'tries' is + * set to 3 then a failed download will be retried upto 2 times. I.e. one + * try and then 2 retries. + * + * @since 3.6.3 + */ + public DownloadManager setTries(int r) { + //Setting to 0 is not sensible because it would mean to just fail without even trying. + Assert.isLegal(r>1); + this.tries = r; + return this; + } + + /** + * Sets the time in milliseconds to wait between retries. The default is 0 meaning no + * wait. + * + * @since 3.6.3 + */ + public void setRetryInterval(long retryInterval) { + Assert.isLegal(retryInterval>=0); + this.retryInterval = retryInterval; + } + + /** + * @since 3.6.3 + */ + public long getRetryInterval(long retryInterval) { + return retryInterval; + } + + public File getCacheDir() { + return cacheDirectory; + } + + public boolean isDownloaded(DownloadableItem item) { + File target = getLocalLocation(item); + return target!=null && target.exists(); + } + + /** + * Dispose this download manager. Optionally delete its cache directory as well. + */ + public void dispose() { + if (deleteCacheOnDispose) { + FileUtils.deleteQuietly(cacheDirectory); + deleteCacheOnDispose = false; + } + } + + /** + * If this is false, an exception will be thrown if download is attempted on + * the UI thread. The default value is true. + */ + public DownloadManager allowUIThread(boolean allow) { + allowUIThread = allow; + return this; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/DownloadableItem.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/DownloadableItem.java new file mode 100644 index 000000000..30d7fd592 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/DownloadableItem.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager; + +import java.io.File; +import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.FileUtils; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.springsource.ide.eclipse.commons.frameworks.core.ExceptionUtil; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; +import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager.DownloadRequestor; + +/** + * A DownloadableItem is something that can be downloaded and + * stored in the local file system as a file. + * + * @author Kris De Volder + */ +public class DownloadableItem { + + private final URL url; + protected final DownloadManager downloader; + private String name; //optional name. If set this name will be used as filename in the cache otherwise + // suitable name will be computed. + protected IStatus downloadStatus = Status.OK_STATUS; //error message if download failed. Otherwise contains 'OK'. + + public DownloadableItem(URL url, DownloadManager downloader) { + this.url = url; + this.downloader = downloader; + } + + public void setFileName(String n) { + this.name = n; + } + + /** + * A downloadable item must provide a URI where its contents can be fetched from. + */ + public URL getURL() { + return url; + } + + /** + * Force the item to be downloaded to a local File. If an item is already downloaded + * the cached local file will be returned immediately. Otherwise the method will block + * until the download is complete or an error occurs. + */ + public File getFile() throws Exception { + try { + final File[] fileBox = new File[1]; + downloader.doWithDownload(this, new DownloadRequestor() { + public void exec(File downloadedFile) throws Exception { + fileBox[0] = downloadedFile; + //TODO; validate file contents? + } + }); + downloadStatus = Status.OK_STATUS; + return fileBox[0]; + } catch (UIThreadDownloadDisallowed e) { + //Shouldn't affect download status since it means download was not attempted + throw e; + } catch (Exception e) { + downloadStatus = error(ExceptionUtil.getMessage(e)); + throw e; + } + } + + protected IStatus error(String message) { + return new Status(IStatus.ERROR, FrameworkCoreActivator.PLUGIN_ID, message); + } + + /** + * A downloadable item must provide a filename where its cached downloaded contents + * should be stored. The name must uniquely identify the downloadable item + * within the scope of the DownloadManager used to download this item. + *

+ * The name must also be a legal file name on the current OS. + *

+ * A default implementation is provided that uses sha1 and Base64 encoding + * to generate a name from the uri. + *

+ * These generated names are not guaranteed to be unique, but the chance of + * a collisions is astronomically small. + */ + protected String getFileName() { + if (name==null) { + try { + MessageDigest sha1encoder = MessageDigest.getInstance("sha1"); + byte[] bytes = sha1encoder.digest((""+getURL()).getBytes()); + name = new String(Base64.encodeBase64(bytes)); + name = name.replace('/', '_'); //slashes are trouble in file names. + } catch (NoSuchAlgorithmException e) { + //This should not be possible + throw new Error(e); + } + } + return name; + } + + @Override + public String toString() { + return url.toString(); + } + + public boolean isDownloaded() { + return downloader!=null && downloader.isDownloaded(this); + } + + /** + * Error message if download failed. Other + * @return + */ + public IStatus getDownloadStatus() { + return downloadStatus; + } + + public void clearCache() { + //Take care not to delete the original file if was local to begin with (in that case we don't + // copy it into the cache dir so there is no cache to clear! + if (url!=null && !"file".equals(url.getProtocol())) { + //Avoid race conditions when someone is downloading this item at the moment. + synchronized (downloader) { + File localFile = downloader.getLocalLocation(this); + FileUtils.deleteQuietly(localFile); + } + } + } + + /** + * Determine the location to unzip to (without actually doing any unzipping). + */ + public File getUnzipDir() { + String fileName = getFileName(); + Assert.isTrue(fileName.endsWith(".zip")); + File unzipLocation = new File(downloader.getCacheDir(), fileName.substring(0, fileName.length()-4)); + return unzipLocation; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/SimpleDownloadService.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/SimpleDownloadService.java new file mode 100644 index 000000000..fef47b178 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/SimpleDownloadService.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager.DownloadManager.DownloadService; +import org.springsource.ide.eclipse.commons.frameworks.core.util.IOUtil; + +public class SimpleDownloadService implements DownloadService { + + private final URLConnectionFactory connectionFactory; + private static final boolean DEBUG = false; + + public SimpleDownloadService() { + this(new URLConnectionFactory()); + } + + public SimpleDownloadService(URLConnectionFactory urlConnectionFactory) { + this.connectionFactory = urlConnectionFactory; + } + + //@Override + public void fetch(URL url, OutputStream writeTo) throws IOException { + URLConnection conn = null; + InputStream input = null; + try { + conn = connectionFactory.createConnection(url); + conn.connect(); + if (DEBUG) { + System.out.println(">>> "+url); + Map> headers = conn.getHeaderFields(); + for (Entry> header : headers.entrySet()) { + System.out.println(header.getKey()+":"); + for (String value : header.getValue()) { + System.out.println(" "+value); + } + } + System.out.println("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"); + } + + input = conn.getInputStream(); + IOUtil.pipe(input, writeTo); + } finally { + if (input!=null) { + try { + input.close(); + } catch (Throwable e) { + //ignore. + } + } + } + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/UIThreadDownloadDisallowed.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/UIThreadDownloadDisallowed.java new file mode 100644 index 000000000..250f00361 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/UIThreadDownloadDisallowed.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager; + + +/** + * Exception thrown by the DownloadManager when a download operation + * is attempted in the UIThread. This is bad practice as it will + * freeze the UI until the download is complete. + * + * @author Kris De Volder + */ +public class UIThreadDownloadDisallowed extends Exception { + + public UIThreadDownloadDisallowed(String string) { + super(string); + } + + private static final long serialVersionUID = 1L; + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/URLConnectionFactory.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/URLConnectionFactory.java new file mode 100644 index 000000000..9ef1e5f28 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/downloadmanager/URLConnectionFactory.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.downloadmanager; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; + +public class URLConnectionFactory { + + public int CONNECT_TIMEOUT = 3000; + + public URLConnection createConnection(URL url) throws IOException { + URLConnection conn = url.openConnection(); + conn.setConnectTimeout(CONNECT_TIMEOUT); + return conn; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/cache/Cache.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/cache/Cache.java new file mode 100644 index 000000000..24345a0ae --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/cache/Cache.java @@ -0,0 +1,15 @@ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.cache; + +/** + * A Cache is basically just some kind of map in which key/value pairs can be kept. + *

+ * Different implementations of this interface may implement different methods of automatically + * expiring cache entries. + */ +public interface Cache { + + V get(K key); + void put(K key, V value); + void clear(); //clear all entries from the cache. + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/cache/LimitedTimeCache.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/cache/LimitedTimeCache.java new file mode 100644 index 000000000..7ddaf0dd5 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/cache/LimitedTimeCache.java @@ -0,0 +1,155 @@ +/******************************************************************************* + * Copyright (c) 2016, 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.cache; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.function.Function; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; + +/** + * A cache that automatically removes entries when they reach a certain age. + * + * @author Kris De Volder + */ +public class LimitedTimeCache implements Cache { + + private static final boolean DEBUG = false; //(""+Platform.getLocation()).contains("kdvolder"); + + private static void debug(String string) { + if (DEBUG) { + System.out.println(string); + } + } + + public final long MAX_AGE; + public final long AGE_MARGIN; + + private Map cache = new HashMap<>(); + + /** + * Job that removes expired entries from the cache. + */ + private Job clearExpiredJob = new Job("Clean expired cache entries") { + + { + setSystem(true); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + long oldest = clearExpired(); + //Reschedule job to again run when the next entry expires. + if (oldest>=0) { + clearExpiredJob.schedule(MAX_AGE - oldest + AGE_MARGIN); + } + return Status.OK_STATUS; + } + }; + + private class Entry { + long lastUsed; + V value; + Entry(V v) { + this.value = v; + this.lastUsed = System.currentTimeMillis(); + } + long age() { + return System.currentTimeMillis() - lastUsed; + } + } + + public LimitedTimeCache(Duration MAX_AGE_DURATION) { + MAX_AGE = MAX_AGE_DURATION.toMillis(); + AGE_MARGIN = Math.max(1000, MAX_AGE / 20); + } + + @Override + public synchronized V get(K key) { + Entry e = cache.get(key); + if (e!=null) { + e.lastUsed = System.currentTimeMillis(); + return e.value; + } + return null; + } + + @Override + public synchronized void put(K key, V value) { + boolean wasEmpty = cache.isEmpty(); + if (value==null) { + cache.remove(key); + } else { + cache.put(key, new Entry(value)); + } + //if the cache was not empty then it is already guaranteed a 'cleaningJob' will run to + // clean the existing oldest entry. + if (wasEmpty && !cache.isEmpty()) { + //The entry we just added is the only one in the cache. + //Therefore, that entry must be the oldest and its age is 0. + //So we know when the job should run next: + clearExpiredJob.schedule(MAX_AGE+AGE_MARGIN); + } + } + + @Override + public synchronized void clear() { + cache.clear(); + } + + /** + * Iterates the cache removing all expired entries. + * @return The age of the oldest non-expired entry in the cache or -1 if the cache is empty. + */ + private synchronized long clearExpired() { + debug(">>> clearExpired MAX_AGE = "+MAX_AGE); + Iterator.Entry>> iter = cache.entrySet().iterator(); + long oldest = -1; + while (iter.hasNext()) { + java.util.Map.Entry.Entry> me = iter.next(); + Entry e = me.getValue(); + long age = e.age(); + if (age>=MAX_AGE) { + debug("Expired: "+me.getKey() +" age: "+e.age()); + iter.remove(); + } else { + oldest = Math.max(oldest, age); + } + } + debug("<<< clearExpired ["+cache.size()+"]"); + return oldest; + } + + /** + * Applies a {@link LimitedTimeCache} cache around a given function. + * + * @param duration time after which entries in the cache should expire. + * @param func Function to wrap the cache around + * @return Equivalent function but with caching. + */ + public static Function applyOn(Duration duration, Function func) { + LimitedTimeCache cache = new LimitedTimeCache<>(duration); + return (k) -> { + V v = cache.get(k); + if (v==null) { + cache.put(k, v=func.apply(k)); + } + return v; + }; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/BooleanParameterDescriptor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/BooleanParameterDescriptor.java new file mode 100644 index 000000000..426399654 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/BooleanParameterDescriptor.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +/** + * @author Nieraj Singh + */ +public class BooleanParameterDescriptor extends ParameterDescriptor { + + private boolean useNameAsValue; + + /** + * Use this constructor if the boolean value is either "true" or "false", meaning that the parameter has a + * pattern: + *

+ * [parametername]=true + *

+ *

+ * [parametername]=false + *

+ * + * @param name + * @param description + * @param isMandatory + * @param defaultValue + * @param requiresNameInCommand + * @param parameterPrefix + * @param valueSeparator + */ + public BooleanParameterDescriptor(String name, String description, + boolean isMandatory, boolean defaultValue, + boolean requiresNameInCommand, String parameterPrefix, + String valueSeparator) { + super(name, description, isMandatory, new Boolean(defaultValue), + ParameterKind.BOOLEAN, requiresNameInCommand, parameterPrefix, + valueSeparator); + useNameAsValue = false; + } + + /** + * User this constructor if the name of the parameter itself is also the + * value. This should be used when the parameter either appears in the command + * string with its name if set to true, or is omitted altogether if the value is false + * + * + * @param name + * @param description + * @param isMandatory + * @param defaultValue + * @param parameterPrefix + */ + public BooleanParameterDescriptor(String name, String description, + boolean isMandatory, boolean defaultValue, String parameterPrefix) { + super(name, description, isMandatory, new Boolean(defaultValue), + ParameterKind.BOOLEAN, true, parameterPrefix, null); + useNameAsValue = true; + } + + /** + * If true, the name of the parameter should be used as a value, instead of + * using "true" or "false". Therefore , in this case, the if the boolean is + * true, the name of the parameter is included, if boolean is false, the + * parameter is omitted. + * + * @return + */ + public boolean useNameAsValue() { + return useNameAsValue; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ComboParameterDescriptor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ComboParameterDescriptor.java new file mode 100644 index 000000000..ab7f498b0 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ComboParameterDescriptor.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +/** + * A combo parameter must specify an ordered list of valid values. If also + * specifying a default value, the value should be part of the list of valid + * values. + * @author Nieraj Singh + */ +public class ComboParameterDescriptor extends ParameterDescriptor { + private String[] values; + + public ComboParameterDescriptor(String name, String description, + boolean isMandatory, Object defaultValue, boolean requiresName, + String delimiter, String valueSeparator, String[] values) { + super(name, description, isMandatory, defaultValue, + ParameterKind.COMBO, requiresName, delimiter, valueSeparator); + this.values = values; + } + + public String[] getSelectionValues() { + return values; + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/CommandFactory.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/CommandFactory.java new file mode 100644 index 000000000..b0887596c --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/CommandFactory.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + + +/** + * Constructs command parameter descriptors, as well as command instances. + * @author Nieraj Singh + * @author Christian Dupuis + */ +public class CommandFactory { + + public static IFrameworkCommand createCommandInstance(IFrameworkCommandDescriptor descriptor) { + if (descriptor == null) { + return null; + } + return new FrameworkCommand(descriptor); + } + + /* + * Command descriptor methods + */ + public static IFrameworkCommandDescriptor createCommandDescriptor(String name, String description, + ICommandParameterDescriptor[] parameters) { + return new FrameworkCommandDescriptor(name, description, parameters); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/CommandParameter.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/CommandParameter.java new file mode 100644 index 000000000..d9f217e69 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/CommandParameter.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +import java.util.ArrayList; +import java.util.List; + +/** + * Basic implementation of a command parameter instance. It contains a reference + * to the parameter descriptor. + * @author Nieraj Singh + */ +public class CommandParameter implements ICommandParameter, + IValueChangedNotifier { + + private Object value; + private ICommandParameterDescriptor descriptor; + private List listeners; + + public CommandParameter(ICommandParameterDescriptor descriptor) { + this.descriptor = descriptor; + init(); + } + + protected void init() { + if (descriptor != null) { + Object defaultValue = descriptor.getDefaultValue(); + if (defaultValue != null) { + // Internal set value. Do not fire events for this + value = defaultValue; + } + } + } + + public ICommandParameterDescriptor getParameterDescriptor() { + return descriptor; + } + + public void setValue(Object value) { + this.value = value; + notifyValueChanged(value); + } + + public Object getValue() { + return value; + } + + public boolean hasValue() { + return value != null; + } + + public IParameterValueListener addListener(IParameterValueListener listener) { + if (listener == null) { + return null; + } + if (listeners != null) { + listeners = new ArrayList(); + } + + if (listeners.contains(listener)) { + return null; + } + + listeners.add(listener); + + return listener; + } + + public IParameterValueListener removeListener( + IParameterValueListener listener) { + if (listeners != null && listeners.remove(listener)) { + return listener; + } + return null; + } + + protected void notifyValueChanged(Object value) { + if (listeners == null) { + return; + } + + ParameterValueEvent event = new ParameterValueEvent(value); + for (IParameterValueListener listener : listeners) { + listener.handleValueChanged(event); + } + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/CompositeCommandParameter.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/CompositeCommandParameter.java new file mode 100644 index 000000000..ed0841347 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/CompositeCommandParameter.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Nieraj Singh + */ +public class CompositeCommandParameter extends CommandParameter { + + private List parameters; + + public CompositeCommandParameter(CompositeParameterDescriptor descriptor) { + super(descriptor); + } + + /** + * Never null. + * + * @return + */ + public List getParameters() { + if (parameters == null) { + parameters = new ArrayList(); + CompositeParameterDescriptor descriptor = (CompositeParameterDescriptor) getParameterDescriptor(); + List childDescriptors = descriptor + .getParameterDescriptors(); + for (ICommandParameterDescriptor child : childDescriptors) { + ICommandParameter parameter = ParameterFactory + .getParameterInstance(child); + if (parameter != null) { + parameters.add(parameter); + } + } + } + return parameters; + } + + /** + * For a composite parameter, the value of the parameter is the value of one + * of it's child parameters whose value has been set. If none of the child + * parameters have value set, null is returned. + */ + public Object getValue() { + ICommandParameter setChildParameter = getSetChildCommandParameter(); + if (setChildParameter != null) { + return setChildParameter.getValue(); + } + return null; + } + + /** + * If a composite parameter has a child parameter with a value set, return + * true. If none of the composite parameter's children have value set, + * return false. + */ + public boolean hasValue() { + return getValue() != null; + } + + /** + * Returns the child command parameter that has a set value. Ideally there + * should only be at most one command parameter with a value, but if more + * than one is encountered, the first one is returned. + * + * @return first command parameter with value set that is found, or null if + * none of the child parameters have value. + */ + public ICommandParameter getSetChildCommandParameter() { + if (parameters == null || parameters.isEmpty()) { + return null; + } + + for (ICommandParameter parameter : parameters) { + // There should at most only be one parameter with a value + // If not, return the first one encountered + if (parameter.hasValue()) { + return parameter; + } + } + return null; + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/CompositeParameterDescriptor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/CompositeParameterDescriptor.java new file mode 100644 index 000000000..a29c4744c --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/CompositeParameterDescriptor.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +import java.util.Collections; +import java.util.List; + +/** + * @author Nieraj Singh + */ +public class CompositeParameterDescriptor extends ParameterDescriptor { + + private List descriptors; + + public CompositeParameterDescriptor(String name, String description, + boolean isMandatory, Object defaultValue, + boolean requiresNameInCommand, String argumentDelimiter, + String valueSeparator, List descriptors) { + super(name, description, isMandatory, defaultValue, + ParameterKind.COMPOSITE, requiresNameInCommand, + argumentDelimiter, valueSeparator); + this.descriptors = descriptors; + } + + /** + * Should never be null. Return empty if it contains no child descriptors + * + * @return + */ + public List getParameterDescriptors() { + return descriptors != null ? descriptors + : (descriptors = Collections.EMPTY_LIST); + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/FrameworkCommand.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/FrameworkCommand.java new file mode 100644 index 000000000..c22c017a9 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/FrameworkCommand.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +import java.util.ArrayList; +import java.util.List; + +/** + * Basic framework command implementation. Represents an INSTANCE of a command. + *

+ * The command description and name are immutable, but the list of parameters is + * mutable. + * @author Nieraj Singh + */ +public class FrameworkCommand implements IFrameworkCommand { + + private IFrameworkCommandDescriptor descriptor; + private List parameters; + + public FrameworkCommand(IFrameworkCommandDescriptor descriptor) { + this.descriptor = descriptor; + } + + public List getParameters() { + if (parameters == null) { + this.parameters = new ArrayList(); + ICommandParameterDescriptor[] parameterDescriptors = descriptor + .getParameters(); + for (ICommandParameterDescriptor paramDescriptor : parameterDescriptors) { + if (paramDescriptor != null) { + ICommandParameter parameter = getCommandParameter(paramDescriptor); + if (parameter != null) { + parameters.add(parameter); + } + } + } + } + return parameters; + } + + protected ICommandParameter getCommandParameter( + ICommandParameterDescriptor descriptor) { + return ParameterFactory.getParameterInstance(descriptor); + } + + public IFrameworkCommandDescriptor getCommandDescriptor() { + return descriptor; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/FrameworkCommandDescriptor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/FrameworkCommandDescriptor.java new file mode 100644 index 000000000..3ad39aa5f --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/FrameworkCommandDescriptor.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +/** + * Basic implementation of a command descriptor. The list of parameters should not be null, although + * it can be empty. + * @author Nieraj Singh + */ +public class FrameworkCommandDescriptor implements IFrameworkCommandDescriptor { + private String name; + private String description; + private ICommandParameterDescriptor[] parameters; + + public FrameworkCommandDescriptor(String name, String description, + ICommandParameterDescriptor[] parameters) { + this.name = name; + this.description = description; + this.parameters = parameters != null ? parameters + : new ICommandParameterDescriptor[] {}; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + /* + * (non-Javadoc) + * @see com.springsource.sts.frameworks.core.internal.commands.IFrameworkCommandDescriptor#getParameters() + */ + public ICommandParameterDescriptor[] getParameters() { + return parameters; + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ICommandListener.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ICommandListener.java new file mode 100644 index 000000000..ba396a0ef --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ICommandListener.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +/** + * Classes implementing this interface get notified when a command is added. + * This is meant to allow users to execute the notifier in a worker thread if + * necessary. A possible usage is to share the ICommandNotifier in a worker + * thread where commands can be added. + *

+ * Note that this adds command descriptors as opposed to command instances. + *

+ * @author Nieraj Singh + */ +public interface ICommandListener { + + /** + * Add a command to the notifier. If null nothing gets added. + * + * @param command + * to add. If null nothing gets added. + */ + public void addCommandDescriptor(IFrameworkCommandDescriptor commandDescriptor); +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ICommandParameter.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ICommandParameter.java new file mode 100644 index 000000000..aa3d9e796 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ICommandParameter.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +/** + * A command parameter instance that contains actual parameter values that can be run in a command + *

+ * The command parameter contains a reference to a parameter descriptor that describes the parameter, like its name, + * description, type, and default value. + *

+ * @author Nieraj Singh + */ +public interface ICommandParameter { + + /** + * Get the parameter descriptor for this parameter instance + * @return + */ + public ICommandParameterDescriptor getParameterDescriptor(); + + /** + * Set the actual value of the parameter. Usually set by the UI. Note that + * this should be stored separately from the default value, even if both are + * the same. + * + * @param actual + * parameter value. + */ + public void setValue(Object value); + + /** + * @return the actual parameter value. This is always a separate value than + * the default + */ + public Object getValue(); + + /** + * + * @return True if it has a value, false otherwise + */ + public boolean hasValue(); + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ICommandParameterDescriptor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ICommandParameterDescriptor.java new file mode 100644 index 000000000..7ac78aaca --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ICommandParameterDescriptor.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +/** + * This describes a parameter descriptor, and is intended to be IMMUTABLE. + *

+ * To instantiate a parameter whose values can be set, see + * {@link ICommandParameter}

+ * Example of how to define a parameter descriptor: + *

+ * -Dsys.env=prod + *

+ *

+ * Here, "-D" is the prefix, "sys.env" the parameter name, "=" the value + * separator, and "prod" a default value + *

+ * @author Nieraj Singh + */ +public interface ICommandParameterDescriptor { + + /** + * This is the name of the parameter as it would appear in the actual + * command line String. It must be exactly as it would be used if a user + * would type it when executing a command in a command line. + * + * @return name of the parameter. It's required + */ + public String getName(); + + /** + * + * @return optional description of the parameter that may include + * information on what it does, and what values may be acceptable + */ + public String getDescription(); + + /** + * + * @return true if it requires a set value, false if it is an optional + * parameter + */ + public boolean isMandatory(); + + /** + * Identifier indicating the type of parameter. Must not be null. + * + * @return non-null indentifier. + */ + public ParameterKind getParameterKind(); + + /** + * Optionally, a parameter can specify a default value. Some parameters may + * always have a default value, like a boolean parameter, in which case the + * default is false. The value of the default is not expected to change + * throughout the life of the parameter, unlike the actual value. + * + * @return a default value, or null if it has no default value. Empty + * strings are considered default values, so only use null to + * indicate no default value + */ + public Object getDefaultValue(); + + /** + * If the parameter name needs to be included in the executable command + * string and must be prefixed by a delimiter like "--", return a non-null + * value. Return null if parameter name should not be prefixed, regardless + * of whether the parameter name itself should appear or not. + * + * @return non-null String if it has a delimiter, null if not. + */ + public String getParameterPrefix(); + + /** + * @return true if the parameter name forms part of the executable String, + * or false if it should be omitted. + */ + public boolean requiresParameterNameInCommand(); + + /** + * Optional. If a parameter requires a value separator that separates the + * value from the parameter name, like for instance '=' or ':', the + * parameter descriptor must return a non-null, non-empty separator. A null + * or empty separator will be ignored. + *

+ * -Dsys.env=prod + *

+ *

+ * Here, "-D" is the prefix, "sys.env" the parameter name, and "=" the value + * separator + *

+ * + * @return the value separator + */ + public String getValueSeparator(); + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IFrameworkCommand.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IFrameworkCommand.java new file mode 100644 index 000000000..ceda25e47 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IFrameworkCommand.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +import java.util.List; + +/** + * Represents a command instance with a list of ordered parameters. Implementors + * can have the command instance generate parameter instances for the parameter + * descriptors contained in the associated command descriptor. + *

+ * Each instance is associated with one command descriptor, and it must hold a + * reference to the command descriptor it is representing + *

+ * @author Nieraj Singh + */ +public interface IFrameworkCommand { + + /** + * Associated command descriptor. Must not be null. + * + * @return + */ + public IFrameworkCommandDescriptor getCommandDescriptor(); + + /** + * List of parameters for this command. Note that order matters, therefore + * the order in which the parameters appear should match the order in which + * they are used as arguments to the command. This is a MUTABLE list, so + * callers can add or remove parameter instances, although the corresponding + * parameter descriptors cannot be modified + * + * @return ordered list of command parameters. May be empty but never null + */ + public List getParameters(); + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IFrameworkCommandDescriptor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IFrameworkCommandDescriptor.java new file mode 100644 index 000000000..7c9963dda --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IFrameworkCommandDescriptor.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + + +/** + * Describes command including its parameters. This is intended to be an IMMUTABLE class, and ideally can be used + * globally to define a particular command. + *

+ * To instantiate a command whose parameter values can be set, use + * {@link IFrameworkCommand}

+ * @author Nieraj Singh + */ +public interface IFrameworkCommandDescriptor { + /** + * Name of the command. Cannot be null. This is the command script name. + */ + public String getName(); + + /** + * Description of the command. Optional. + */ + public String getDescription(); + + /** + * List of parameter descriptors that describe the parameters of this command. + *

+ * Note that this is order-sensitive, therefore the order in which they appear affects how + * the parameters are constructed in a command string. + *

+ * @return + */ + public ICommandParameterDescriptor[] getParameters(); + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IParameterFactory.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IParameterFactory.java new file mode 100644 index 000000000..e12f8ca15 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IParameterFactory.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +/** + * Allows specific domains to implement their own parameter instance factory that gets invoked by + * the command framework. + * @author Nieraj Singh + */ +public interface IParameterFactory { + + /** + * Given a parameter descriptor, generate a parameter instance. This method is typically called by the framework so clients + * only need to make sure that their descriptor adapts to their factory for the generic command framework to invoke the factory. + * + * @param descriptor + * @return command parameter instance corresponding to the descriptor + */ + public ICommandParameter getParameter(ICommandParameterDescriptor descriptor); + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IParameterValueListener.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IParameterValueListener.java new file mode 100644 index 000000000..36aa35d6a --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IParameterValueListener.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +/** + * Parameter instances can implement to be notified when a parameter value has + * changed. This is usually used with Composite parameters, where the value + * change of one child parameter should notify other child parameters + * @author Nieraj Singh + */ +public interface IParameterValueListener { + + /** + * A parameter value has changed, allowing listeners to handle the change + * event. + * + * @param event + */ + public void handleValueChanged(ParameterValueEvent event); + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IValueChangedNotifier.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IValueChangedNotifier.java new file mode 100644 index 000000000..9598ee51b --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/IValueChangedNotifier.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +/** + * Notifies listeners when parameter values have changed. + * @author Nieraj Singh + */ +public interface IValueChangedNotifier { + + public IParameterValueListener addListener(IParameterValueListener listener); + + public IParameterValueListener removeListener( + IParameterValueListener listener); + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/JavaParameterDescriptor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/JavaParameterDescriptor.java new file mode 100644 index 000000000..e99bdb0fe --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/JavaParameterDescriptor.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +import org.eclipse.jdt.core.IJavaElement; + +/** + * Java parameter, where a java element type must be specified and may be used for content assist and other + * operations that require a Java type. + *

+ * The default value can be a qualified Java name, although it is optional. + * @author Nieraj Singh + */ +public class JavaParameterDescriptor extends ParameterDescriptor { + + /** + * List of valid Java types. + */ + public static final int FLAG_INTERFACE = 1 << 2; + + public static final int FLAG_CLASS = 1 << 3; + + public static final int FLAG_PACKAGE = 1 << 4; + + private int type; + + /** + * + * @param name + * of the parameter. required + * @param description + * description is optional. + * @param isMandatory + * if value is needed, set to true, false otherwise + * @param defaultValue + * @param type + * a IJavaElement type identifier, that indicates whether + * possible content assist should be for packages, types, etc.. + */ + public JavaParameterDescriptor(String name, String description, + boolean isMandatory, Object defaultValue, ParameterKind kind, + int type, boolean requiresName, String delimiter, String valueSeparator) { + super(name, description, isMandatory, defaultValue, + ParameterKind.JAVA_TYPE, requiresName, delimiter, valueSeparator); + this.type = type; + } + + /** + * Get the java element type for this parameter. This may be used for + * content assist to decide what type of java element types to search for. + * + * @see IJavaElement + * @return java element type. + */ + public int getJavaElementType() { + return type; + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ParameterDescriptor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ParameterDescriptor.java new file mode 100644 index 000000000..24e5086c3 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ParameterDescriptor.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +import org.eclipse.core.runtime.PlatformObject; + +/** + * Base implementation of a parameter descriptor. + * @author Nieraj Singh + */ +public class ParameterDescriptor extends PlatformObject implements + ICommandParameterDescriptor { + + private String name; + private String description; + private boolean isMandatory; + private Object defaultValue; + private ParameterKind kind; + private boolean requiresNameInCommand = false; + private String parameterPrefix; + private String valueSeparator; + + public ParameterDescriptor(String name, String description, + boolean isMandatory, Object defaultValue, ParameterKind kind, + boolean requiresNameInCommand, String parameterPrefix, + String valueSeparator) { + super(); + this.name = name; + this.description = description; + this.isMandatory = isMandatory; + this.defaultValue = defaultValue; + this.kind = kind; + this.requiresNameInCommand = requiresNameInCommand; + this.parameterPrefix = parameterPrefix; + this.valueSeparator = valueSeparator; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public boolean isMandatory() { + return isMandatory; + } + + public Object getDefaultValue() { + return defaultValue; + } + + public ParameterKind getParameterKind() { + return kind; + } + + public String getParameterPrefix() { + return parameterPrefix; + } + + public String getValueSeparator() { + return valueSeparator; + } + + public boolean requiresParameterNameInCommand() { + return requiresNameInCommand; + } + + public Object getAdapter(Class adapter) { + // Ask the adapter manager first + Object adapterObj = super.getAdapter(adapter); + + // If no adapter can be found, use the default factory + if (adapterObj == null && adapter == IParameterFactory.class) { + adapterObj = new ParameterFactory(); + } + return adapterObj; + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ParameterFactory.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ParameterFactory.java new file mode 100644 index 000000000..07c5b85ac --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ParameterFactory.java @@ -0,0 +1,315 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +import java.util.List; + +import org.eclipse.core.runtime.IAdaptable; + +/** + * Base implementation of a parameter factory that descriptors adapt to. Clients + * can implement their own adapter factory and contribute it through platform + * adapter manager. Users should not be instantiating this class directly, as + * the adapter framework will manage factory lookup. + *

+ * A static method is provided that users can call to obtain a parameter via the + * adapter manager framework. + *

+ * @author Nieraj Singh + */ +public class ParameterFactory implements IParameterFactory { + + /** + * Users should not instantiate this class or invoke this method directly. + * Use the static method instead, as it leverages the adapter manager + * framework. + */ + public ICommandParameter getParameter(ICommandParameterDescriptor descriptor) { + if (descriptor == null) { + return null; + } + + ParameterKind kind = descriptor.getParameterKind(); + switch (kind) { + case BASE: + case JAVA_TYPE: + return new StringCommandParameter(descriptor); + case BOOLEAN: + case COMBO: + return new CommandParameter(descriptor); + case COMPOSITE: + return new CompositeCommandParameter( + (CompositeParameterDescriptor) descriptor); + } + return null; + } + + /** + * A composite parameter is a parameter whose single value can be obtained + * by different types of parameter descriptors. However, a composite value + * always has just one value, just like any other parameter. + * + * @param name + * of the command exactly as it should appear when executed. + * Cannot be null or empty. + * @param description + * optional description + * @param isMandatory + * true if it is required. False otherwise + * @param defaultValue + * optional. If not required, use null. + * @param requiresName + * true if the name of the parameter should appear in the Command + * string + * @param prefix + * non-null if a prefix like "--" should be prepended to the + * parameter name. Null otherwise + * @param valueSeparator + * non-null if the value of the parameter requires a value + * separator like "=". Null otherwise + * @param descriptors + * list of descriptors that define this composite. + * @return + */ + public static ICommandParameterDescriptor createCompositeParameterDescriptor( + String name, String description, boolean isMandatory, + String defaultValue, boolean requiresName, String delimiter, + String valueSeparator, List descriptors) { + return new CompositeParameterDescriptor(name, description, isMandatory, + defaultValue, requiresName, delimiter, valueSeparator, + descriptors); + } + + /** + * + * Use this constructor if the boolean value is either "true" or "false", + * meaning that the parameter has a pattern: + *

+ * [parametername]=true + *

+ *

+ * [parametername]=false + *

+ * Boolean parameters ALWAYS have a default value (true or false) + * + * @param name + * of the command exactly as it should appear when executed. + * Cannot be null or empty. + * @param description + * optional description + * @param isMandatory + * true if it is required. False otherwise + * @param defaultValue + * . True or false + * @param requiresName + * true if the name of the parameter should appear in the Command + * string + * @param prefix + * non-null if a prefix like "--" should be prepended to the + * parameter name. Null otherwise + * @param valueSeparator + * non-null if the value of the parameter requires a value + * separator like "=". Null otherwise + */ + public static ICommandParameterDescriptor createBooleanParameterDescriptor( + String name, String description, boolean isMandatory, + boolean defaultValue, boolean requiresName, String prefix, + String valueSeparator) { + return new BooleanParameterDescriptor(name, description, isMandatory, + defaultValue, requiresName, prefix, valueSeparator); + } + + /** + * User this constructor if the name of the parameter itself is also the + * value. This should be used when the parameter either appears in the + * command string with its name if set to true, or is omitted altogether if + * the value is false + * + *

+ * + * parameter name: "windows". If true, it appears as: "--windows" in the + * command string. If false, it is omitted. The "--" needs to be defined as + * the prefix + *

+ * Boolean parameters ALWAYS have a default value (true or false) + * + * @param name + * of the command exactly as it should appear when executed. + * Cannot be null or empty. + * @param description + * optional description + * @param isMandatory + * true if it is required. False otherwise + * @param defaultValue + * . True or false + * @param prefix + * non-null if a prefix like "--" should be prepended to the + * parameter name. Null otherwise + */ + public static ICommandParameterDescriptor createBooleanParameterDescriptor( + String name, String description, boolean isMandatory, + boolean defaultValue, String prefix) { + return new BooleanParameterDescriptor(name, description, isMandatory, + defaultValue, prefix); + } + + /** + * Basic text-based parameter. + * + * @param name + * of the command exactly as it should appear when executed. + * Cannot be null or empty. + * @param description + * optional description + * @param isMandatory + * true if it is required. False otherwise + * @param defaultValue + * . Null if it has no default value + * @param requiresName + * true if the name of the parameter should appear in the Command + * string + * @param prefix + * non-null if a prefix like "--" should be prepended to the + * parameter name. Null otherwise + * @param valueSeparator + * non-null if the value of the parameter requires a value + * separator like "=". Null otherwise + * @return + */ + public static ICommandParameterDescriptor createBaseParameterDescriptor( + String name, String description, boolean isMandatory, + String defaultValue, boolean requiresName, String prefix, + String valueSeparator) { + return new ParameterDescriptor(name, description, isMandatory, + defaultValue, ParameterKind.BASE, requiresName, prefix, + valueSeparator); + } + + /** + * Creates a Java parameter that pertains to a Java type. The specified Java + * type allows UI components to support the correct type of content assist, + * type browsing and filtering + * + * @param name + * of the command exactly as it should appear when executed. + * Cannot be null or empty. + * @param description + * optional description + * @param isMandatory + * true if it is required. False otherwise + * @param defaultValue + * . Null if it has no default value + * @param requiresName + * true if the name of the parameter should appear in the Command + * string + * @param prefix + * non-null if a prefix like "--" should be prepended to the + * parameter name. Null otherwise + * @param valueSeparator + * non-null if the value of the parameter requires a value + * separator like "=". Null otherwise + * @param type + * must be a valid Java type that this parameter corresponds to. + * This may be used for content assist and Java type browsing. + * See valid values: {@link JavaParameterDescriptor} + * @return + */ + public static ICommandParameterDescriptor createJavaParameterDescriptor( + String name, String description, boolean isMandatory, + String defaultValue, boolean requiresName, String prefix, + String valueSeparator, int type) { + return new JavaParameterDescriptor(name, description, isMandatory, + defaultValue, ParameterKind.JAVA_TYPE, type, requiresName, + prefix, valueSeparator); + } + + /** + * Creates a combo descriptor for the given set of values. + * + * @param name + * of the command exactly as it should appear when executed. + * Cannot be null or empty. + * @param description + * optional description + * @param isMandatory + * true if it is required. False otherwise + * @param defaultValue + * . Null if it has no default value + * @param requiresName + * true if the name of the parameter should appear in the Command + * string + * @param prefix + * non-null if a prefix like "--" should be prepended to the + * parameter name. Null otherwise + * @param valueSeparator + * non-null if the value of the parameter requires a value + * separator like "=". Null otherwise + * @param values + * list of values that appear in the Combo. Cannot be null or + * empty. + * @return + */ + public static ICommandParameterDescriptor createComboParameterDescriptor( + String name, String description, boolean isMandatory, + String defaultValue, boolean requiresName, String prefix, + String valueSeparator, String[] values) { + return new ComboParameterDescriptor(name, description, isMandatory, + defaultValue, requiresName, prefix, valueSeparator, values); + } + + /** + * Creates a parameter descriptor that follows this pattern: + * "--[parametername]=[value]". + * + *

+ * If the parameter is serialised, it is prefixed by "--", followed by the + * exact parameter name, followed by "=" followed by the value. + *

+ * + * @param name + * of the command exactly as it should appear when executed. + * Cannot be null or empty. + * @param description + * optional description + * @param isMandatory + * true if it is required. False otherwise + * @param defaultValue + * . Null if it has no default value + * @return new Command descriptor for the given arguments. + */ + public static ICommandParameterDescriptor createBasePrefixedParameterDescriptor( + String name, String description, boolean isMandatory, + String defaultValue) { + return createBaseParameterDescriptor(name, description, isMandatory, + defaultValue, true, "--", "="); + } + + /** + * Users should use this method to obtain a parameter instance for a given + * parameter descriptor + * + * @param descriptor + * @return new parameter instance for the given parameter descriptor, or + * null if it couldn't be created + */ + public static ICommandParameter getParameterInstance( + ICommandParameterDescriptor descriptor) { + if (descriptor instanceof IAdaptable) { + IParameterFactory factory = (IParameterFactory) ((IAdaptable) descriptor) + .getAdapter(IParameterFactory.class); + if (factory != null) { + return factory.getParameter(descriptor); + } + } + return null; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ParameterKind.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ParameterKind.java new file mode 100644 index 000000000..78c1798df --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ParameterKind.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +/** + * Represents the different types of parameters. + * @author nisingh + * + */ +public enum ParameterKind { + + // Parameter with a String value + BASE, + + // Parameter with a set of defined values + COMBO, + + // Parameter with a Java type value + JAVA_TYPE, + + // Boolean parameter + BOOLEAN, + + // A composite parameter + COMPOSITE + + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ParameterValueEvent.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ParameterValueEvent.java new file mode 100644 index 000000000..b054c7842 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/ParameterValueEvent.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +/** + * @author Nieraj Singh + */ +public class ParameterValueEvent { + + private Object value; + + public ParameterValueEvent(Object value) { + this.value = value; + } + + public Object getValueChanged() { + return value; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/StringCommandParameter.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/StringCommandParameter.java new file mode 100644 index 000000000..6f4e8b6a6 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/StringCommandParameter.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +/** + * A String command parameter where a null OR empty String means there is no value. + * @author Nieraj Singh + */ +public class StringCommandParameter extends CommandParameter { + + public StringCommandParameter(ICommandParameterDescriptor descriptor) { + super(descriptor); + } + + public boolean hasValue() { + boolean hasVal = super.hasValue(); + Object value = getValue(); + if (hasVal && value instanceof String) { + hasVal &= ((String) value).length() > 0; + } + return hasVal; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/SystemPropertyParameterDescriptor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/SystemPropertyParameterDescriptor.java new file mode 100644 index 000000000..d7afaffbc --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/commands/SystemPropertyParameterDescriptor.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.commands; + +/** + * System property parameter descriptor that has "-D" prefix and a value separator "=". + * It is a base type text parameter, therefore has a free text field (no filters or content assist) + * @author Nieraj Singh + */ +public class SystemPropertyParameterDescriptor extends ParameterDescriptor { + + public static final String SYSTEM_PREFIX = "-D"; + public static final String VALUE_SEPARATOR = "="; + + public SystemPropertyParameterDescriptor(String name, String description, + boolean isMandatory, Object defaultValue) { + super(name, description, isMandatory, defaultValue, ParameterKind.BASE, + true, SYSTEM_PREFIX, VALUE_SEPARATOR); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/java/FrameworksJavaUtils.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/java/FrameworksJavaUtils.java new file mode 100644 index 000000000..9cb6e7789 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/java/FrameworksJavaUtils.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.java; + +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaModelException; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; + + +/** + * Common Java utility methods. + * @author Nieraj Singh + */ +public class FrameworksJavaUtils { + + private FrameworksJavaUtils() { + // Utility class + } + + /** + * Obtain the package fragment roots of the first encountered source class + * path entry in the given java project. Return null if the java project is + * null, or no source class path entries can be found. + * + * @param javaProject + * containing source class path entries + * @return package fragment roots of first encountered source class path + * entry, or null otherwise + */ + public static IPackageFragmentRoot[] getFirstEncounteredSourcePackageFragmentRoots( + IJavaProject javaProject) { + if (javaProject == null) { + return null; + } + + try { + + IClasspathEntry[] entries = javaProject.getRawClasspath(); + + for (IClasspathEntry entry : entries) { + + if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { + return javaProject.findPackageFragmentRoots(entry); + } + } + + } catch (JavaModelException e) { + FrameworkCoreActivator.logError(e.getLocalizedMessage(), e); + } + return null; + + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/plugins/BasePluginData.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/plugins/BasePluginData.java new file mode 100644 index 000000000..99ca0fd12 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/plugins/BasePluginData.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins; + +/** + * Very basic metadata for a plugin that only contains property information. + * + * @author Nieraj Singh + */ +public class BasePluginData { + + private String name; + private String version; + private String runtimeVersion; + private String author; + private String title; + private String description; + private String documentation; + + public void setName(String name) { + this.name = name; + } + + public void setDocumentation(String documentation) { + this.documentation = documentation; + } + + /** + * Must never be null. Use empty string if null + * @param version + */ + public void setVersion(String version) { + this.version = version; + } + + public void setRuntimeVersion(String runtimeVersion) { + this.runtimeVersion = runtimeVersion; + } + + public void setAuthor(String author) { + this.author = author; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setDescription(String description) { + this.description = description; + } + + /** + * Must never be null. + * @return + */ + public String getName() { + return name; + } + + /** + * Never null, but may be empty. + * @return + */ + public String getVersion() { + return version; + } + + public String getDocumentation() { + return documentation; + } + + public String getRuntimeVersion() { + return runtimeVersion; + } + + public String getAuthor() { + return author; + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((version == null) ? 0 : version.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + BasePluginData other = (BasePluginData) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (version == null) { + if (other.version != null) + return false; + } else if (!version.equals(other.version)) + return false; + return true; + } + + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/plugins/Plugin.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/plugins/Plugin.java new file mode 100644 index 000000000..c862a60d1 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/plugins/Plugin.java @@ -0,0 +1,308 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a published plugin model, which among other things, + * contains a list of published versions. + *

+ * It specifies the latest version of the plugin as it would be installed IF the + * plugin were installed without specifying a version number. NOTE that this is + * NOT the same as being the most recent version of the plugin that is + * available. There may be newer milestone versions available OR newer versions + * that are only compatible with newer runtimes. Therefore this model provides two + * API for retrieving new versions: + *

  • + * The latest version available for the given version of a runtime used by a + * project. This is the version that is installed if a user installs the plugin + * WITHOUT specifying a version number.
  • + *
  • + * The most recent version added, which may be a milestone or only available for + * newer versions of a runtime, which a user must manually specify when installing + * the plugin. As the list of versions are ORDERED, the most recent version + * added version should be the last version that was added to the list.
  • + *

    + *

    + * This model represents both a published plugin as well as an in-place plugin. + * In-place plugins usually only have one child version (i.e the only version + * that is available), and this version is considered the latest version. + *

    + * + * @author Nieraj Singh + * @author Andrew Eisenberg + */ +public class Plugin { + + /** + * never null + */ + private List versions = new ArrayList(); + + private PluginVersion latestVersion; + + private String name; + + /** + * Default is always false, meaning when created it is assumed it is + * published + */ + private boolean isInplace = false; + + public Plugin(String name) { + this.name = name; + } + + /** + * Returns a COPY of an ORDERED list of versions, where the first entry is + * the oldest and the last the most recent. The last entry does NOT + * necessarily mean the version that will get install if the plugin were + * to be installed without specifying a version number. It just means the + * most recent version that is available for this plugin. The + * "latest version" (the version that installs when no version number + * is specified) may in fact be an older version than the most recent + * version that was added. + * + * @return all versions of this plugin, including new milestones. List is + * ordered, with the most recent entry being the last entry. Its + * never null, and should not be empty as a plugin has at least one + * version + */ + public List getVersions() { + return new ArrayList(versions); + } + + /** + * Ordered addition of a version. If the version doesn't already exist and + * is not null, it is added at the end of the list and true is returned if + * successfully added. + *

    + * Any version that is successfully added also has its parent set to this + * instance. + *

    + * + * @param version + * to add to the end of the list. The version must correspond to + * the same plugin (i.e must have the same plugin name) + * @return true if successfully added. False otherwise + */ + public boolean addVersion(PluginVersion version) { + if (version != null && !versions.contains(version) + && getName().equals(version.getName())) { + versions.add(version); + version.setParent(this); + return true; + } + return false; + } + + public void removeVersion(PluginVersion version) { + versions.remove(version); + } + + /** + * Gets the version model for the given ID, or null if not found + * + * @param versionID + * @return + */ + public PluginVersion getVersion(String versionID) { + if (versionID == null) { + return null; + } + for (PluginVersion version : getVersions()) { + if (versionID.equals(version.getVersion())) { + return version; + } + } + return null; + } + + /** + * + * @return true if it is in place, false if it is published. + */ + public boolean isInPlace() { + return isInplace; + } + + /** + * Set to true ONLY if this plugin has not been published. + * + * @param isInplace + * true if it is in-place, false if it is published + */ + public void setIsInPlace(boolean isInplace) { + this.isInplace = isInplace; + } + + public boolean isInstalled() { + return getInstalled() != null; + } + + public PluginVersion getInstalled() { + for (PluginVersion version : getVersions()) { + if (version.isInstalled()) { + return version; + } + } + return null; + } + + /** + * Will set the latest version IF the latest version already exists in the + * list of version, OR if it does not exist, it is successfully added at the + * end of the list. Returns true if latest version successfully set. False + * otherwise. + *

    + * Note that this CANNOT be null. + *

    + * + * @param latestVersion + * latest released version + * @return true if successfully set, false otherwise + */ + public boolean setLatestReleasedVersion(PluginVersion latestVersion) { + if (versions.contains(latestVersion) || addVersion(latestVersion)) { + this.latestVersion = latestVersion; + //Note: it is possible that latestVersion was a 'duplicate' element. So although it + // is equals it may not be the same object. That means the parent could remain null + // unless we explicitly set it here. This causes NPE later on. + // See: STS-2490 + this.latestVersion.setParent(this); + return true; + } + return false; + } + + /** + * This may be different and more recent thatn the lastest released version + * + * @return the most recent version of the plugin that is available. This may + * OR may not be the same as the latest version available. + */ + public PluginVersion getMostRecentVersionAdded() { + List children = getVersions(); + if (children != null && !children.isEmpty()) { + return children.get(children.size() - 1); + } + return null; + } + + /** + * Gets the latest version of this plugin, but may NOT necessarily be the + * most recent version. The "latest version" is defined as the version + * that installs when NO version number is specified when installing the + * plugin. This version is set when this plugin model is first + * created , and is considered the "latest version" for the particular + * version of the runtime that a project is using, although newer versions may + * exist. This cannot be null. + * + * @return latest version. Should not be null. + */ + public PluginVersion getLatestReleasedVersion() { + return latestVersion; + } + + /** + * Given a plugin, check if it is a version of this pluging. Return false if + * it is not a version of this plugin. + * + * @param pluginToCheck + * @return true if version. False otherwise. + */ + public boolean isVersion(PluginVersion pluginToCheck) { + if (pluginToCheck == null) { + return false; + } + String nameToCheck = pluginToCheck.getName(); + String versionToCheck = pluginToCheck.getVersion(); + List versions = getVersions(); + if (versions != null && versionToCheck != null && nameToCheck != null + && nameToCheck.equals(getName())) { + for (PluginVersion ver : versions) { + if (versionToCheck.equals(ver.getVersion())) { + return true; + } + } + } + return false; + + } + + public String getName() { + return name; + } + + /** + * Determines whether an update exists for the installed version. The + * lastest version available is the version that installs when the + * install-plugin command is run without specifying a version number. + * However, this may not necessarily be the newest version, as newer + * milestone versions may exist, as well as versions that are only + * compatible with newer runtimes. + *

    + * If a plugin is installed to the latest version, but newer milestone + * versions are available, this method returns false. It only returns true + * if the plugin is installed at an older version, but if the user were to + * run the install plugin command, a more recent released version would be + * installed instead. + *

    + * + * @return true if and only if the plugin is installed and a newer released + * version exists. If the plugin is not installed or it is installed + * with the latest released version, this returns false. + */ + public boolean hasUpdate() { + // Should never be null + PluginVersion latestVersion = getLatestReleasedVersion(); + PluginVersion installedVersion = getInstalled(); + if (installedVersion == null) { + return false; + } + return isVersionHigher(latestVersion, installedVersion); + } + + /** + * Checks if the first plugin is higher than the second plugin argument. + * False otherwise, including if one or the other or both arguments are + * null. True if and only if both version are NOT null and the first + * version is higher than the second. + * + * @param data1 + * @param data2 + * @return true if data1 is higher than data2 + */ + public static boolean isVersionHigher(PluginVersion data1, + PluginVersion data2) { + String version1 = data1.getVersion(); + String version2 = data2.getVersion(); + if (version1 != null && version2 != null) { + return version1.compareToIgnoreCase(version2) > 0; + } + return false; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(name); + if (isInplace) { + sb.append(" (in-place)"); + } + return sb.toString(); + } + + + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/plugins/PluginService.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/plugins/PluginService.java new file mode 100644 index 000000000..011cc2297 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/plugins/PluginService.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins; + +import java.util.List; + +/** + * An abstract implementation of a service for managing plug-ins. + * + * @author Steffen Pingel + */ +public abstract class PluginService { + + public enum InstallOrUpgradeStatus { + SUCCESS, FAILED, INVALID_REPOSITORY_URL, VERIFICATION_NEEDED + } + + public abstract InstallOrUpgradeStatus install(PluginVersion plugin); + + public abstract List search(String searchTerms, boolean refresh, + boolean trustedOnly, boolean compatibleOnly); + + public abstract InstallOrUpgradeStatus remove(PluginVersion plugin); + + public abstract InstallOrUpgradeStatus upgrade(PluginVersion plugin); + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/plugins/PluginVersion.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/plugins/PluginVersion.java new file mode 100644 index 000000000..e46259d34 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/internal/plugins/PluginVersion.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins; + +/** + * Model element that represents a version of a plugin. It is usually a + * child of a plugin model element. + * + * @author Nieraj Singh + * @author Kris De Volder + */ +public class PluginVersion extends BasePluginData { + + private Plugin parent; + private boolean isInstalled; + + public PluginVersion() { + // + } + + public PluginVersion(BasePluginData data) { + setData(data); + } + + public void setParent(Plugin parent) { + this.parent = parent; + } + + public Plugin getParent() { + return parent; + } + + public void setInstalled(boolean isInstalled) { + this.isInstalled = isInstalled; + } + + public boolean isInstalled() { + return isInstalled; + } + + /** + * Support for copying a dependency data into the version model. + * + * @param data + */ + protected void setData(BasePluginData data) { + if (data == null) { + return; + } + setName(data.getName()); + setAuthor(data.getAuthor()); + setDescription(data.getDescription()); + setDocumentation(data.getDocumentation()); + setRuntimeVersion(data.getRuntimeVersion()); + setVersion(data.getVersion()); + setTitle(data.getTitle()); + } + + @Override + public String toString() { + return getName()+"["+getVersion()+"]"; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/legacyconversion/AbstractLegacyConverter.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/legacyconversion/AbstractLegacyConverter.java new file mode 100644 index 000000000..acbc83895 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/legacyconversion/AbstractLegacyConverter.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.legacyconversion; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.util.Properties; +import java.util.Map.Entry; + +import org.eclipse.core.internal.utils.FileUtil; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; + +/** + * Shared methods between the two converter classes + * @author Andrew Eisenberg + * @since 3.0.0 + */ +public abstract class AbstractLegacyConverter implements IConversionConstants { + public abstract IStatus convert(IProgressMonitor monitor); + + protected void copyPluginStateLocation(String from, + String to) throws IOException { + File oldPreferencesFolder = FrameworkCoreActivator.getDefault().getStateLocation().removeLastSegments(1).append(from).toFile(); + if (oldPreferencesFolder.exists() && oldPreferencesFolder.isDirectory()) { + File newPreferencesFolder = FrameworkCoreActivator.getDefault().getStateLocation().removeLastSegments(1).append(to).toFile(); + copyDirectory(oldPreferencesFolder, newPreferencesFolder); + } + } + + /** + * @param oldFolder + * @param newFolder + * @throws IOException + */ + private void copyDirectory(File oldFolder, File newFolder) + throws IOException { + if (!newFolder.exists()) { + newFolder.mkdir(); + } + + // copy everything and delete old so we never have to do this again + for (File oldFile : oldFolder.listFiles()) { + String oldName = oldFile.getName(); + String newName = oldName.replace(GRAILS_OLD_PLUGIN_NAME, GRAILS_NEW_PLUGIN_NAME); + newName = newName.replace(ROO_OLD_PLUGIN_NAME, ROO_NEW_PLUGIN_NAME); + File newFile = new File(newFolder, newName); + if (oldFile.isDirectory()) { + copyDirectory(new File(oldFolder, oldName), new File(newFolder, newName)); + } else { + copyFile(oldFile, newFile); + } + } + } + /** + * Copies a preference file from the old location to the new one, replacing prefixes of the appropriate settings + * + * @param settingsFile the original settings file to copy + * @param newSettingsFile the new settings file to create + * @param oldPrefix old prefix of keys to migrate set to null if not using prefixes + * @param newPrefix new key prefix + */ + protected void copyPreferencesFile(File settingsFile, File newSettingsFile, String oldPrefix, String newPrefix) throws IOException, + CoreException, FileNotFoundException { + if (settingsFile.exists()) { + Properties oldProps = new Properties(); + oldProps.load(new FileInputStream(settingsFile)); + Properties newProps = new Properties(); + for (Entry prop : oldProps.entrySet()) { + String oldKey = prop.getKey().toString(); + String newKey; + if (oldPrefix != null && oldKey.startsWith(oldPrefix)) { + newKey = oldKey.replace(oldPrefix, newPrefix); + } else { + newKey = oldKey; + } + newProps.put(newKey, prop.getValue()); + } + newProps.store(new FileOutputStream(newSettingsFile), ""); //$NON-NLS-1$ + } + } + + public static void copyFile(File sourceFile, File destFile) + throws IOException { + if (!sourceFile.exists()) { + return; + } + if (!destFile.exists()) { + destFile.createNewFile(); + } + + FileChannel source = null; + FileChannel destination = null; + + try { + source = new FileInputStream(sourceFile).getChannel(); + destination = new FileOutputStream(destFile).getChannel(); + destination.transferFrom(source, 0, source.size()); + } finally { + if (source != null) { + source.close(); + } + if (destination != null) { + destination.close(); + } + } + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/legacyconversion/IConversionConstants.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/legacyconversion/IConversionConstants.java new file mode 100644 index 000000000..387afc9b2 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/legacyconversion/IConversionConstants.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2012, 2020 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.legacyconversion; + +/** + * Set of constants to be used to + * convert legacy (pre-3.0) STS projects to 3.0 projects. + * @author Andrew Eisenberg + * @since 3.0.0 + */ +public interface IConversionConstants { + String AUTO_CHECK_FOR_LEGACY_STS_PROJECTS = "org.springsource.ide.eclipse.commons.frameworks.ui.legacyconversion.autocheck"; //$NON-NLS-1$ + String LEGACY_MIGRATION_ALREADY_DONE = "org.springsource.ide.eclipse.commons.frameworks.ui.legacyconversion.done"; //$NON-NLS-1$ + // comma separated list of plugins that already have run plugin-specific legacy conversion code + String LEGACY_MIGRATION_PLUGINS = "org.springsource.ide.eclipse.commons.frameworks.ui.legacyconversion.plugins"; //$NON-NLS-1$ + + // GRAILS + String GRAILS_OLD_PREFERENCE_PREFIX = "com.springsource.sts.grails"; //$NON-NLS-1$ + String GRAILS_NEW_PREFERENCE_PREFIX = "org.grails.ide.eclipse"; //$NON-NLS-1$ + + String GRAILS_OLD_PLUGIN_NAME = GRAILS_OLD_PREFERENCE_PREFIX + ".core"; //$NON-NLS-1$ + String GRAILS_NEW_PLUGIN_NAME = GRAILS_NEW_PREFERENCE_PREFIX + ".core"; //$NON-NLS-1$ + + String GRAILS_OLD_NATURE = GRAILS_OLD_PLUGIN_NAME + ".nature"; //$NON-NLS-1$ + String GRAILS_NEW_NATURE = GRAILS_NEW_PLUGIN_NAME + ".nature"; //$NON-NLS-1$ + + String GRAILS_OLD_CONTAINER = GRAILS_OLD_PLUGIN_NAME + ".CLASSPATH_CONTAINER"; //$NON-NLS-1$ + String GRAILS_NEW_CONTAINER = GRAILS_NEW_PLUGIN_NAME + ".CLASSPATH_CONTAINER"; //$NON-NLS-1$ + + String GRAILS_OLD_ATTRIBUTE = GRAILS_OLD_PLUGIN_NAME + ".SOURCE_FOLDER"; //$NON-NLS-1$ + String GRAILS_NEW_ATTRIBUTE = GRAILS_NEW_PLUGIN_NAME + ".SOURCE_FOLDER"; //$NON-NLS-1$ + + String GRAILS_OLD_PERSPECTIVE_ID = "com.springsource.sts.grails.perspective"; //$NON-NLS-1$ + String GRAILS_NEW_PERSPECTIVE_ID = "org.grails.ide.eclipse.perspective"; //$NON-NLS-1$ + + // GRADLE + String GRADLE_OLD_PREFIX = "com.springsource.sts.gradle"; //$NON-NLS-1$ + String GRADLE_NEW_PREFIX = "org.springsource.ide.eclipse.gradle"; //$NON-NLS-1$ + String GRADLE_OLD_PLUGIN_NAME = "com.springsource.sts.gradle.core"; //$NON-NLS-1$ + String GRADLE_NEW_PLUGIN_NAME = "org.springsource.ide.eclipse.gradle.core"; //$NON-NLS-1$ + String GRADLE_OLD_NATURE = GRADLE_OLD_PLUGIN_NAME + ".nature"; //$NON-NLS-1$ + String GRADLE_NEW_NATURE = GRADLE_NEW_PLUGIN_NAME + ".nature"; //$NON-NLS-1$ + + // ROO + String ROO_OLD_NATURE = "com.springsource.sts.roo.core.nature"; //$NON-NLS-1$ + String ROO_NEW_NATURE = "com.springsource.sts.roo.core.nature"; //$NON-NLS-1$ + + String ROO_OLD_PLUGIN_NAME = "com.springsource.sts.roo.core"; //$NON-NLS-1$ + String ROO_NEW_PLUGIN_NAME = "org.springframework.ide.eclipse.roo.core"; //$NON-NLS-1$ + + String ROO_OLD_UI_NAME = "com.springsource.sts.roo.ui"; //$NON-NLS-1$ + String ROO_NEW_UI_NAME = "org.springframework.ide.eclipse.roo.ui"; //$NON-NLS-1$ + + // OTHER + String STS_OLD_CONTENT_CORE = "com.springsource.sts.content.core"; //$NON-NLS-1$ + String STS_NEW_CONTENT_CORE = "org.springsource.ide.eclipse.commons.content.core"; //$NON-NLS-1$ + + String STS_OLD_CORE = "com.springsource.sts.core"; //$NON-NLS-1$ + String STS_NEW_CORE = "org.springsource.ide.eclipse.commons.core"; //$NON-NLS-1$ + + String STS_OLD_IDE_UI = "com.springsource.sts.ide.ui"; //$NON-NLS-1$ + String STS_NEW_IDE_UI = "org.springsource.ide.eclipse.dashboard.ui"; //$NON-NLS-1$ + + String[] STS_OLD_WORKSPACE_PREFS = new String[] { + "com.springsource.sts.grails.core", //$NON-NLS-1$ + "com.springsource.sts.grails.ui", //$NON-NLS-1$ + "com.springsource.sts.grails.editor.gsp", //$NON-NLS-1$ + "com.springsource.sts.grails.explorer", //$NON-NLS-1$ + "com.springsource.sts.grails.refactoring", //$NON-NLS-1$ + "com.springsource.sts.grails.groovy.debug.core", //$NON-NLS-1$ + "com.springsource.sts.config.ui", //$NON-NLS-1$ + "com.springsource.sts.core", //$NON-NLS-1$ + // "com.springsource.sts.gradle.core", Gradle does its own thing. + "com.springsource.sts.groovy.debug.core", //$NON-NLS-1$ + "com.springsource.sts.ide.metadata", //$NON-NLS-1$ + "com.springsource.sts.maven", //$NON-NLS-1$ + "com.springsource.sts.ide.osgi", //$NON-NLS-1$ + "com.springsource.sts.ide.ui", //$NON-NLS-1$ + //"com.springsource.sts.config.flow", //$NON-NLS-1$ + }; + String[] STS_NEW_WORKSPACE_PREFS = new String[] { + "org.grails.ide.eclipse.core", //$NON-NLS-1$ + "org.grails.ide.eclipse.ui", //$NON-NLS-1$ + "org.grails.ide.eclipse.editor.gsp", //$NON-NLS-1$ + "org.grails.ide.eclipse.explorer", //$NON-NLS-1$ + "org.grails.ide.eclipse.refactoring", //$NON-NLS-1$ + "org.grails.ide.eclipse.groovy.debug.core", //$NON-NLS-1$ + "org.springframework.ide.eclipse.config.ui", //$NON-NLS-1$ + "org.springsource.ide.eclipse.commons.core", //$NON-NLS-1$ +// "org.springsource.ide.eclipse.gradle.core", //$NON-NLS-1$ + "org.grails.ide.eclipse.groovy.debug.core", //$NON-NLS-1$ + "org.springframework.ide.eclipse.metadata", //$NON-NLS-1$ + "org.springframework.ide.eclipse.maven", //$NON-NLS-1$ + "org.springframework.ide.eclipse.osgi.runtime", //$NON-NLS-1$ + "org.springsource.ide.eclipse.dashboard.ui", //$NON-NLS-1$ + //"org.springframework.ide.eclipse.config.graph" //$NON-NLS-1$ + }; + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/legacyconversion/LegacyProjectConverter.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/legacyconversion/LegacyProjectConverter.java new file mode 100644 index 000000000..3c4ea28f3 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/legacyconversion/LegacyProjectConverter.java @@ -0,0 +1,222 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.legacyconversion; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Path; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.jdt.core.IClasspathAttribute; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; + +/** + * Converts legacy maven projects to the new m2e + * @author Andrew Eisenberg + * @since 3.0.0 + */ +public class LegacyProjectConverter extends AbstractLegacyConverter implements IConversionConstants { + + private final List allLegacyProjects; + private IProject[] selectedLegacyProjects; + + /** + * Converts a single project + */ + public LegacyProjectConverter(IProject legacyProject) { + allLegacyProjects = Collections.singletonList(legacyProject); + selectedLegacyProjects = new IProject[] { legacyProject }; + } + + public LegacyProjectConverter(List legacyProjects) { + this.allLegacyProjects = legacyProjects; + } + + public List getAllLegacyProjects() { + return allLegacyProjects; + } + + public IProject[] getSelectedLegacyProjects() { + return selectedLegacyProjects; + } + + public void setSelectedLegacyProjects(IProject[] selectedLegacyProjects) { + this.selectedLegacyProjects = selectedLegacyProjects; + } + + public IStatus convert(IProgressMonitor monitor) { + if (monitor == null) { + monitor = new NullProgressMonitor(); + } + SubMonitor sub = SubMonitor.convert(monitor, selectedLegacyProjects.length); + IStatus[] statuses = new IStatus[selectedLegacyProjects.length]; + int i = 0; + for (IProject project : selectedLegacyProjects) { + if (project.isAccessible()) { + sub.subTask("Converting " + project.getName()); //$NON-NLS-1$ + if (sub.isCanceled()) { + throw new OperationCanceledException(); + } + statuses[i++] = convert(project, monitor); + } else { + // project was closed before job started. + statuses[i++] = Status.OK_STATUS; + } + sub.worked(1); + } + + return new MultiStatus(FrameworkCoreActivator.PLUGIN_ID, 0, statuses, "Result of converting legacy maven projects", null); //$NON-NLS-1$ + } + + + private IStatus convert(IProject project, IProgressMonitor monitor) { + SubMonitor sub = SubMonitor.convert(monitor, 1); + // grab project rule + Job.getJobManager().beginRule(ResourcesPlugin.getWorkspace().getRoot(), sub); + try { + if (project.hasNature(GRAILS_OLD_NATURE)) { + convertGrailsProject(project, sub); + } else if (project.hasNature(ROO_OLD_NATURE)) { + convertRooProject(project, sub); + } else if (project.hasNature(GRADLE_OLD_NATURE)) { + convertGradleProject(project, sub); + } + } catch (Exception e) { + return new Status(IStatus.ERROR, FrameworkCoreActivator.PLUGIN_ID, "Failed to convert " + project.getName(), e); //$NON-NLS-1$ + } finally { + // release rule + Job.getJobManager().endRule(ResourcesPlugin.getWorkspace().getRoot()); + } + sub.worked(1); + return new Status(IStatus.OK, FrameworkCoreActivator.PLUGIN_ID, "Converted " + project.getName()); //$NON-NLS-1$ + } + + private static void convertGradleProject(IProject project, SubMonitor sub) throws Exception { + // nature + IProjectDescription description = project.getDescription(); + String[] ids = description.getNatureIds(); + List newIds = new ArrayList(ids.length); + for (int i = 0; i < ids.length; i++) { + if (!ids[i].equals(GRADLE_OLD_NATURE) && !ids[i].equals(GRADLE_NEW_NATURE)) { + newIds.add(ids[i]); + } else { + newIds.add(GRADLE_NEW_NATURE); + } + } + description.setNatureIds(newIds.toArray(new String[0])); + project.setDescription(description, sub); + + // project preferences + // DO NOTHING: gradle tooling handles these itself by reading in both old and new locations. + + // classpath container + IJavaProject javaProject = JavaCore.create(project); + IClasspathEntry[] classpath = javaProject.getRawClasspath(); + List newClasspath = new ArrayList(); + for (int i = 0; i < classpath.length; i++) { + IClasspathEntry entry = classpath[i]; + if (entry.getEntryKind()==IClasspathEntry.CPE_CONTAINER) { + String path = entry.getPath().toString(); + if (path.contains(GRADLE_OLD_PREFIX)) { + entry = JavaCore.newContainerEntry(new Path(path.replace(GRADLE_OLD_PREFIX, GRADLE_NEW_PREFIX)), + entry.getAccessRules(), entry.getExtraAttributes(), entry.isExported()); + } + } + newClasspath.add(entry); + } + javaProject.setRawClasspath(newClasspath.toArray(new IClasspathEntry[0]), sub); + } + + private void convertGrailsProject(IProject project, SubMonitor sub) throws Exception { + // nature + IProjectDescription description = project.getDescription(); + String[] ids = description.getNatureIds(); + List newIds = new ArrayList(ids.length); + for (int i = 0; i < ids.length; i++) { + if (!ids[i].equals(GRAILS_OLD_NATURE) && !ids[i].equals(GRAILS_NEW_NATURE)) { + newIds.add(ids[i]); + } else { + newIds.add(GRAILS_NEW_NATURE); + } + } + description.setNatureIds(newIds.toArray(new String[0])); + project.setDescription(description, sub); + + // project preferences + IFolder preferencesFolder = project.getFolder(".settings/"); //$NON-NLS-1$ + File settingsFile = preferencesFolder.getFile(GRAILS_OLD_PLUGIN_NAME + ".prefs").getLocation().toFile(); //$NON-NLS-1$ //$NON-NLS-2$ + File newSettingsFile = preferencesFolder.getFile(GRAILS_NEW_PLUGIN_NAME + ".prefs").getLocation().toFile(); //$NON-NLS-1$ //$NON-NLS-2$ + copyPreferencesFile(settingsFile, newSettingsFile, GRAILS_OLD_PREFERENCE_PREFIX, GRAILS_NEW_PREFERENCE_PREFIX); + InstanceScope.INSTANCE.getNode(GRAILS_OLD_PLUGIN_NAME).sync(); + preferencesFolder.refreshLocal(IResource.DEPTH_ONE, sub); + + // classpath container + IJavaProject javaProject = JavaCore.create(project); + IClasspathEntry[] classpath = javaProject.getRawClasspath(); + List newClasspath = new ArrayList(); + for (int i = 0; i < classpath.length; i++) { + if (classpath[i].getPath().toString().equals(GRAILS_OLD_CONTAINER)) { + newClasspath.add(JavaCore.newContainerEntry(new Path(GRAILS_NEW_CONTAINER), classpath[i].getAccessRules(), convertGrailsClasspathAttributes(classpath[i]), classpath[i].isExported())); + } else if (classpath[i].getEntryKind() == IClasspathEntry.CPE_SOURCE) { + newClasspath.add(JavaCore.newSourceEntry(classpath[i].getPath(), classpath[i].getInclusionPatterns(), + classpath[i].getExclusionPatterns(), classpath[i].getOutputLocation(), convertGrailsClasspathAttributes(classpath[i]))); + } else { + newClasspath.add(classpath[i]); + } + } + javaProject.setRawClasspath(newClasspath.toArray(new IClasspathEntry[0]), sub); + } + + private IClasspathAttribute[] convertGrailsClasspathAttributes( + IClasspathEntry entry) { + IClasspathAttribute[] oldAttributes = entry.getExtraAttributes(); + if (oldAttributes == null || oldAttributes.length == 0) { + return new IClasspathAttribute[0]; + } + IClasspathAttribute[] newAttributes = new IClasspathAttribute[oldAttributes.length]; + for (int i = 0; i < oldAttributes.length; i++) { + if (oldAttributes[i].getName().equals(GRAILS_OLD_ATTRIBUTE)) { + newAttributes[i] = JavaCore.newClasspathAttribute(GRAILS_NEW_ATTRIBUTE, oldAttributes[i].getValue()); + } else { + newAttributes[i] = oldAttributes[i]; + } + } + + return newAttributes; + } + + private void convertRooProject(IProject project, SubMonitor sub) throws Exception { + IFolder preferencesFolder = project.getFolder(".settings/"); //$NON-NLS-1$ + File settingsFile = preferencesFolder.getFile(ROO_OLD_PLUGIN_NAME + ".prefs").getLocation().toFile(); //$NON-NLS-1$ //$NON-NLS-2$ + File newSettingsFile = preferencesFolder.getFile(ROO_NEW_PLUGIN_NAME + ".prefs").getLocation().toFile(); //$NON-NLS-1$ //$NON-NLS-2$ + copyPreferencesFile(settingsFile, newSettingsFile, ROO_OLD_PLUGIN_NAME, ROO_NEW_PLUGIN_NAME); + InstanceScope.INSTANCE.getNode(ROO_OLD_PLUGIN_NAME).sync(); + preferencesFolder.refreshLocal(IResource.DEPTH_ONE, sub); + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/legacyconversion/LegacyWorkspaceConverter.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/legacyconversion/LegacyWorkspaceConverter.java new file mode 100644 index 000000000..b4a77a259 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/legacyconversion/LegacyWorkspaceConverter.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.legacyconversion; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.core.internal.runtime.InternalPlatform; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.eclipse.jface.preference.IPreferenceStore; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; + +/** + * Converts legacy 2.x workspace preferences into 3.x preferences + * @author Andrew Eisenberg + * @since 3.0.0 + */ +public class LegacyWorkspaceConverter extends AbstractLegacyConverter implements IConversionConstants { + private static final IPreferenceStore PREFERENCE_STORE = FrameworkCoreActivator.getDefault().getPreferenceStore(); + + public boolean shouldAutoConvert() { + return ! PREFERENCE_STORE.getBoolean(LEGACY_MIGRATION_ALREADY_DONE); + } + + public IStatus convert(IProgressMonitor monitor) { + if (monitor == null) { + monitor = new NullProgressMonitor(); + } + SubMonitor sub = SubMonitor.convert(monitor, 4); + + IStatus[] statuses = new IStatus[4]; + + statuses[0] = copyPluginPreferences(sub); + // nothing to do for grails any more +// statuses[1] = convertGrailsWorkspacePreferences(sub); + statuses[1] = Status.OK_STATUS; + statuses[2] = convertRooWorkspacePreferences(sub); + statuses[3] = convertSTSPreferences(sub); + + IStatus result = new MultiStatus(FrameworkCoreActivator.PLUGIN_ID, 0, statuses, + "Result of converting legacy STS 2.x workspace preferences to 3.x", null); //$NON-NLS-1$ + // FrameworkCoreActivator.getDefault().getLog().log(result); + if (result.isOK()) { + PREFERENCE_STORE.setValue(LEGACY_MIGRATION_ALREADY_DONE, true); + } + return result; + } + + + /** + * Copies preferences inside of the + * @param sub + * @return + */ + private IStatus copyPluginPreferences(SubMonitor sub) { + sub.subTask("Copying plugin preferences for legacy STS workspace"); //$NON-NLS-1$ + try { + File prefsFolder = InternalPlatform.getDefault().getRuntimeInstance().getStateLocation().toFile(); + prefsFolder = new File(prefsFolder, ".settings"); + for (int i = 0; i < STS_OLD_WORKSPACE_PREFS.length; i++) { + if (STS_NEW_WORKSPACE_PREFS[i].equals("???")) { //$NON-NLS-1$ + continue; + } + copyPreferencesFile(new File(prefsFolder, STS_OLD_WORKSPACE_PREFS[i] + ".prefs"), new File(prefsFolder, STS_NEW_WORKSPACE_PREFS[i] + ".prefs"), STS_OLD_WORKSPACE_PREFS[i], STS_NEW_WORKSPACE_PREFS[i]); //$NON-NLS-1$ //$NON-NLS-2$ + InstanceScope.INSTANCE.getNode(STS_NEW_WORKSPACE_PREFS[i]).sync(); + } + } catch (Exception e) { + return new Status(IStatus.ERROR, FrameworkCoreActivator.PLUGIN_ID, "Failed to convert legacy STS workspace preferences", e); //$NON-NLS-1$ + } + return new Status(IStatus.OK, FrameworkCoreActivator.PLUGIN_ID, "Converted legacy STS plugin preferences"); //$NON-NLS-1$ + } + + private IStatus convertSTSPreferences(SubMonitor sub) { + sub.subTask("Converting STS plugin state locations"); //$NON-NLS-1$ + try { + copyPluginStateLocation(STS_OLD_CONTENT_CORE, STS_NEW_CONTENT_CORE); + copyPluginStateLocation(STS_OLD_CORE, STS_NEW_CORE); + copyPluginStateLocation(STS_OLD_IDE_UI, STS_NEW_IDE_UI); + return new Status(IStatus.OK, FrameworkCoreActivator.PLUGIN_ID, "Converted legacy STS plugin state locations"); //$NON-NLS-1$ + } catch (IOException e) { + return new Status(IStatus.ERROR, FrameworkCoreActivator.PLUGIN_ID, "Failed to convert legacy STS plugin state locations", e); //$NON-NLS-1$ + } finally { + sub.worked(1); + } + } + + // TODO FIXADE Probably safe to delete +// private IStatus convertGrailsWorkspacePreferences(SubMonitor sub) { +// sub.subTask("Converting Grails plugin state locations"); //$NON-NLS-1$ +// try { +// copyPluginStateLocation(GRAILS_OLD_PREFERENCE_PREFIX, GRAILS_NEW_PREFERENCE_PREFIX); +// return new Status(IStatus.OK, FrameworkCoreActivator.PLUGIN_ID, "Converted legacy Grails plugin state locations"); //$NON-NLS-1$ +// } catch (IOException e) { +// return new Status(IStatus.ERROR, FrameworkCoreActivator.PLUGIN_ID, "Failed to convert legacy Grails plugin state locations", e); //$NON-NLS-1$ +// } finally { +// sub.worked(1); +// } +// } + + private IStatus convertRooWorkspacePreferences(SubMonitor sub) { + sub.subTask("Converting Roo plugin state locations"); //$NON-NLS-1$ + try { + // Let RooInstallManager migrate the roo.installs content + // copyPluginStateLocation(ROO_OLD_PLUGIN_NAME, ROO_NEW_PLUGIN_NAME); + copyPluginStateLocation(ROO_OLD_UI_NAME, ROO_NEW_UI_NAME); + return new Status(IStatus.OK, FrameworkCoreActivator.PLUGIN_ID, "Converted legacy Roo plugin state locations"); //$NON-NLS-1$ + } catch (IOException e) { + return new Status(IStatus.ERROR, FrameworkCoreActivator.PLUGIN_ID, "Failed to convert legacy Roo plugin state locations", e); //$NON-NLS-1$ + } finally { + sub.worked(1); + } + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/maintype/IMainTypeFinder.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/maintype/IMainTypeFinder.java new file mode 100644 index 000000000..0582a4e26 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/maintype/IMainTypeFinder.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.maintype; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IType; + +public interface IMainTypeFinder { + + /** + * Finds main types in a given Java project. If no types are found, an empty + * value is returned. + * + * @param project + * java project where main types should be searched. + * @param mon + * @return non-null list of main types in the project. Return empty value if + * no main types are found. + * @throws Exception + * if failure occurred while finding main types + */ + public IType[] findMain(IJavaProject project, IProgressMonitor mon) + throws Exception; + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/maintype/MainTypeFinder.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/maintype/MainTypeFinder.java new file mode 100644 index 000000000..ebf45ec74 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/maintype/MainTypeFinder.java @@ -0,0 +1,252 @@ +/******************************************************************************* + * Copyright (c) 2012, 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.maintype; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.internal.debug.ui.launcher.MainMethodSearchEngine; +import org.springsource.ide.eclipse.commons.frameworks.core.ExceptionUtil; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; + +/** + * Provides access to heuristic algorithms to 'guess' the main type for a + * project. + * + * Note: these algorithms may depend on project type and require access to + * various optional dependencies. E.g. gradle tooling for gradle projects, m2e + * for pom based algorithms etc. + *

    + * Ideally in the future additional algorithms should be contributable via + * extension point so that we don't need to add a gazilion dependencies in this + * plugin, but clients can depend on this plugin and implicitly gain access if + * the required stuff is installed. + * + * @author Kris De Volder + */ +@SuppressWarnings("restriction") +public class MainTypeFinder { + + private static MainTypeFinder instance; + + private static final IType[] EMPTY = new IType[0]; + + /** + * Find the most likely main types that should be run when a user select this + * project with 'run as'. + * + * @return Types with main method or empty value if the 'most likely' main type + * can't be determined. + * @throws CoreException + * if something unexpected happened.. + */ + public static IType[] guessMainTypes(IJavaProject project, IProgressMonitor mon) + throws CoreException { + return getInstance().internalGuessMainTypes(project, mon); + } + + private static synchronized MainTypeFinder getInstance() { + if (instance == null) { + instance = new MainTypeFinder(); + } + return instance; + } + + private Map algos; + + /** + * Singelton, use getInstance or call one of the static public methods. + */ + private MainTypeFinder() { + // TODO: support contributing additional algos with priorities via + // extension point + // Created as linked hash map so that iteration starts from highest to + // lowest confidence + this.algos = new LinkedHashMap(); + + // Add finders based on whether they return main types based on the confidence that + // they are exact types requested. For example, main types from pom files have high + // confidence that they are the types requested, whereas types from source have lower + // confidence that they are the types requested. Each confidence itself also has a priority + // order, with more authoritative finders listed first. + this.algos.put(Confidence.CERTAIN, new IMainTypeFinder[] { + // add in order of which is more authoritative + }); + + this.algos.put(Confidence.HIGH, new IMainTypeFinder[] { + // add more here if they should be considered more authorative + new FindInPom() + // add more here if they should be considered less authorative + }); + + this.algos.put(Confidence.LOW, new IMainTypeFinder[] { + // add more here if they should be considered more authorative + new FindInSource() + // add more here if they should be considered less authorative + }); + + } + + private IType[] internalGuessMainTypes(IJavaProject project, IProgressMonitor mon) + throws CoreException { + Throwable error = null; + mon.beginTask("Search main types", algos.size()); + Set totalTypes = new LinkedHashSet(); + + try { + + for(Entry entry : algos.entrySet()) { + // Collect all types per confidence level + for (IMainTypeFinder algo : entry.getValue()) { + try { + IType[] found = algo.findMain(project, new SubProgressMonitor(mon, 1)); + for (IType type : found) { + totalTypes.add(type); + } + } catch (Throwable e) { + if (error == null) { + // if multiple errors keep only the first one. + error = e; + } + } + } + // Stop when types of the highest confidence are found + if (!totalTypes.isEmpty()) { + break; + } + } + + mon.worked(1); + } finally { + mon.done(); + } + if (!totalTypes.isEmpty()) { + return totalTypes.toArray(new IType[0]); + } else { + // If there was an error throw it as an explanation of the failure. + if (error != null) { + throw ExceptionUtil.coreException(error); + } + } + return EMPTY; + } + + private static final String MAIN_CLASS_PROP = "start-class"; + + public static class FindInPom implements IMainTypeFinder { + + public IType[] findMain(IJavaProject jp, IProgressMonitor mon) + throws Exception { + mon.beginTask("Search main in pom", 1); + try { + IProject p = jp.getProject(); + IFile pomFile = p.getFile("pom.xml"); + if (pomFile.exists()) { + PomParser pomParser = new PomParser(pomFile); + String starterClassName = pomParser + .getProperty(MAIN_CLASS_PROP); + if (starterClassName != null) { + IType mainType = jp.findType(starterClassName); + if (mainType != null) { + return new IType[] { mainType }; + } + throw ExceptionUtil.coreException("'pom.xml' defines '" + + MAIN_CLASS_PROP + "' as '" + starterClassName + + "' but it could not be found"); + } + } + } finally { + mon.done(); + } + return EMPTY; + } + } + + public static class FindInSource implements IMainTypeFinder { + + public IType[] findMain(IJavaProject javaProject, + IProgressMonitor monitor) throws Exception { + monitor.beginTask("Search main types in project source", 1); + try { + + MainMethodSearchEngine engine = new MainMethodSearchEngine(); + IJavaSearchScope scope = createSearchScope(javaProject); + + boolean includeSubtypes = true; + IType[] types = engine + .searchMainMethods(monitor, scope, includeSubtypes); + if (types == null) { + types = EMPTY; + } + return types; + } catch (Exception e) { + FrameworkCoreActivator.log(e); + } finally { + monitor.done(); + } + return EMPTY; + } + + private IJavaSearchScope createSearchScope(IJavaProject javaProject) throws JavaModelException { + List pfrs = new ArrayList<>(); + for (IPackageFragmentRoot pfr : javaProject.getAllPackageFragmentRoots()) { + IClasspathEntry cpe = pfr.getRawClasspathEntry(); + if (isInteresting(cpe)) { + pfrs.add(pfr); + } + } + + int constraints = IJavaSearchScope.SOURCES; + constraints |= IJavaSearchScope.APPLICATION_LIBRARIES; + + return SearchEngine.createJavaSearchScope( + pfrs.toArray(new IJavaElement[pfrs.size()]), constraints); + } + + private boolean isInteresting(IClasspathEntry cpe) { + if (cpe==null) { + return false; + } + int e_kind = cpe.getEntryKind(); + return e_kind == IClasspathEntry.CPE_SOURCE + || ( + e_kind == IClasspathEntry.CPE_CONTAINER && + "org.jetbrains.kotlin.core.KOTLIN_CONTAINER".equals(cpe.getPath().segment(0)) + ); + } + } + + /** + * Confidence that a search result for main types in a project is what is expected. + * + */ + public static enum Confidence { + CERTAIN, HIGH, LOW + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/maintype/PomParser.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/maintype/PomParser.java new file mode 100644 index 000000000..9c625537e --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/maintype/PomParser.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.maintype; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.runtime.CoreException; +import org.springsource.ide.eclipse.commons.frameworks.core.ExceptionUtil; +import org.springsource.ide.eclipse.commons.frameworks.core.util.XmlUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +//TODO: maybe we should use m2e in future. For now its simple enough we can do this ourselves +// and avoid a mandatory dependency on m2e. + +/** + * Wrapper around a pom file. Can parse the pom and retrieve interesting data from it. + * It caches a Document internally so it avoids repeatedly parsing the same file + * for repeated info requests. + */ +class PomParser { + + private Document doc; + private HashMap properties = null; //extracted from doc when first prop is requested. + + private void parse(InputStream input) throws ParserConfigurationException, SAXException, IOException { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + this.doc = dBuilder.parse(input); + } + + + public PomParser(IFile pomFile) throws CoreException { + parse(pomFile); + } + + + private void parse(IFile file) throws CoreException { + InputStream input = null; + try { + parse(input = file.getContents(true)); + } catch (Exception e) { + throw ExceptionUtil.coreException(e); + } finally { + if (input!=null) { + try { + input.close(); + } catch (IOException e) { + } + } + } + } + + public String getProperty(String name) { + Map props = getProperties(); + return props.get(name); + } + + + private Map getProperties() { + if (properties==null) { + properties = new HashMap(); + NodeList propLists = doc.getElementsByTagName("properties"); + //There should really only be one propList but anyhoo! + for (int i = 0; i < propLists.getLength(); i++) { + Node propList = propLists.item(i); + NodeList propNodes = propList.getChildNodes(); + for (int j = 0; j < propNodes.getLength(); j++) { + Node propNode = propNodes.item(j); + String name = XmlUtils.getTagName(propNode); + if (name!=null) { + String value = propNode.getTextContent(); + properties.put(name, value); + } + } + } + } + return properties; + } + + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/ArrayEncoder.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/ArrayEncoder.java new file mode 100644 index 000000000..b00ea73d2 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/ArrayEncoder.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.util; + +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Utilty methods for encoding / decoding an array of Strings into a single String. + * + * @author Kris De Volder + */ +public class ArrayEncoder { + + private static final char ESCAPE_CHAR = '\\'; + private static final char TERMINATOR_CHAR = ';'; + private static final int BUFFER_SIZE = 1024; + + /** + * Decode an array of Strings encoded as a single String by StringUtils encodeArray. + */ + public static String[] decode(String encoded) { + if (encoded==null) { + return null; + } + // Encoding: String are terminated by a special terminator char. If the original String contains a separator + // character or an escape character it is escaped by preceding it with an escape character. + + // Note: we use terminator char rather than a separator between Strings. + // This is because with a true separator, it will be impossible to distinguish between the encoding of an empty array + // and the encoding of an array with a single empty String. + + // Examples: (terminator = ';' escape = '\\') + + // new String[0] ==encode==> "" + // new String[] { "" } ==encode==> ";" + // new String[] { "Hello", "World" } ==encode==> "Hello;World;" + + StringReader in = new StringReader(encoded); + List strings = new ArrayList(); + StringBuffer currentString = new StringBuffer(); + try { + for (int _c = in.read(); _c>=0; _c = in.read()) { + char c = (char)_c; + if (c==TERMINATOR_CHAR) { + strings.add(currentString.toString()); + currentString = new StringBuffer(); + } else if (c==ESCAPE_CHAR) { + c = (char)in.read(); + currentString.append(c); + } else { + currentString.append(c); + } + } + } catch (Exception e) { + throw new IllegalStateException(e); + } + return strings.toArray(new String[strings.size()]); + } + + public static String encode(String[] strings) { + StringWriter out = new StringWriter(BUFFER_SIZE); + for (String string : strings) { + for (int i = 0; i < string.length(); i++) { + char c = string.charAt(i); + if (c==ESCAPE_CHAR || c==TERMINATOR_CHAR) { + out.write(ESCAPE_CHAR); + } + out.write(c); + } + out.write(TERMINATOR_CHAR); + }; + return out.toString(); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/DocumentRegion.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/DocumentRegion.java new file mode 100644 index 000000000..572b9fcaa --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/DocumentRegion.java @@ -0,0 +1,393 @@ +/******************************************************************************* + * Copyright (c) 2020 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITypedRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TypedRegion; +import org.springsource.ide.eclipse.commons.livexp.util.ExceptionUtil; + +/** + * A non-sucky alternative to {@link IRegion}. Represents a region of text in a document. + *

    + * Caution: assumes the underlying document is not mutated during the lifetime of the + * region object (otherwise start/end positions may no longer be valid). + *

    + * Implements {@link CharSequence} for convenience (e.g you can use {@link DocumentRegion} as + * input to a {@link Pattern} and other standard JRE functions which expect a {@link CharSequence}. + * + * @author Kris De Volder + */ +public class DocumentRegion implements CharSequence { + final IDocument doc; + final int start; + final int end; + public DocumentRegion(IDocument doc, IRegion r) { + this(doc, + r.getOffset(), + r.getOffset()+r.getLength() + ); + } + + /** + * Constructs a {@link DocumentRegion} on a given document. Tries its + * best to behave sensibly when passed 'strange' coordinates by + * adjusting them logically rather than throw an Exception. + *

    + * A position before the start of the document is moved to be the start + * of the document. + *

    + * A position after the end of the document is moved to the end + * of the document. + *

    + * If 'end' position is before the start position it is moved be + * exactly at the start position (this avoids region with + * negative length). + */ + public DocumentRegion(IDocument doc, int start, int end) { + this.doc = doc; + this.start = limitRange(start, 0, doc.getLength()); + this.end = limitRange(end, start, doc.getLength()); + } + + /** + * Create {@link DocumentRegion} covering the whole document. + */ + public DocumentRegion(IDocument doc) { + this(doc, 0, doc.getLength()); + } + + private int limitRange(int offset, int min, int max) { + if (offsetmax) { + return max; + } + return offset; + } + + @Override + public String toString() { + return textBetween(doc, start, end); + } + + public DocumentRegion trim() { + return trimEnd().trimStart(); + } + + public DocumentRegion trimStart() { + int howMany = 0; + int len = length(); + while (howMany0) { + return subSequence(0, len-howMany); + } + return this; + } + + /** + * Gets character from the region, offset from the start of the region + * @return the character from the document (char)0 if the offset is outside the region. + */ + public char unsafeCharAt(int offset) { + if (offset<0 || offset>=length()) { + throw new IndexOutOfBoundsException(""+offset); + } + try { + return doc.getChar(start+offset); + } catch (BadLocationException e) { + throw new IndexOutOfBoundsException(""+offset); + } + } + + /** + * Like charAt, but doesn't IndexOutOfBoundsException. Instead it + * return (char)0. + */ + @Override + public char charAt(int offset) { + try { + return unsafeCharAt(offset); + } catch (IndexOutOfBoundsException e) { + return 0; + } + } + + @Override + public int length() { + return end-start; + } + + @Override + public DocumentRegion subSequence(int start, int end) { + int len = length(); + Assert.isLegal(start>=0); + Assert.isLegal(end<=len); + if (start==0 && end==len) { + return this; + } + return new DocumentRegion(doc, this.start+start, this.start+end); + } + + public boolean isEmpty() { + return length()==0; + } + + public DocumentRegion subSequence(int start) { + return subSequence(start, length()); + } + + public IRegion asRegion() { + return new Region(start, end-start); + } + + public ITypedRegion asTypedRegion(String type) { + return new TypedRegion(start, length(), type); + } + + + public int indexOf(char ch, int fromIndex) { + while (fromIndex < length()) { + if (charAt(fromIndex)==ch) { + return fromIndex; + } + fromIndex++; + } + return -1; + } + + public DocumentRegion[] split(char c) { + List pieces = new ArrayList<>(); + int start = 0; + int end; + while ((end=indexOf(c, start))>=0) { + pieces.add(subSequence(start, end)); + start = end+1; + } + // Do not forget the last piece! + pieces.add(subSequence(start, length())); + return pieces.toArray(new DocumentRegion[pieces.size()]); + } + + public DocumentRegion[] split(Pattern delimiter) { + List pieces = new ArrayList<>(); + int start = 0; + Matcher matcher = delimiter.matcher(this); + while (matcher.find(start)) { + int end = matcher.start(); + pieces.add(subSequence(start, end)); + start = matcher.end(); + } + // Do not forget the last piece! + pieces.add(subSequence(start, length())); + return pieces.toArray(new DocumentRegion[pieces.size()]); + } + + /** + * Removes a single occurrence of pat from the start of this region. + */ + public DocumentRegion trimStart(Pattern pat) { + pat = Pattern.compile("^("+pat.pattern()+")"); + Matcher matcher = pat.matcher(this); + if (matcher.find()) { + return subSequence(matcher.end()); + } + return this; + } + + /** + * Removes a single occurrence of pat from the end of this region. + */ + public DocumentRegion trimEnd(Pattern pat) { + pat = Pattern.compile("("+pat.pattern()+")$"); + Matcher matcher = pat.matcher(this); + if (matcher.find()) { + return subSequence(0, matcher.start()); + } + return this; + } + + /** + * Get the region after this one with a given lenght. + *

    + * If the document is too short to provide the requested lenght + * then the region is truncated to end of the document. + */ + public DocumentRegion textAfter(int len) { + Assert.isLegal(len>=0); + return new DocumentRegion(doc, end, Integer.MAX_VALUE == len ? Integer.MAX_VALUE : end+len); + } + + /** + * Get the region before this one with a given lenght. + *

    + * If the requested region extends before the start of the document, + * then the region is shortened so its start coincides with document start. + */ + public DocumentRegion textBefore(int len) { + Assert.isLegal(len>=0); + return new DocumentRegion(doc, start-len, start); + } + + public IDocument getDocument() { + return doc; + } + + /** + * Get the start of this region in 'absolute' terms (i.e. relative to the document). + */ + public int getStart() { + return start; + } + + /** + * Get the end of this region in 'absolute' terms (i.e. relative to the document). + */ + public int getEnd() { + return end; + } + + /** + * Convert the given document offset into an offset relative to this region. + */ + public int toRelative(int offset) { + return offset-start; + } + + public int getLength() { + return length(); + } + + public boolean endsWith(CharSequence string) { + int myLen = length(); + int strLen = string.length(); + if (myLen>=strLen) { + for (int i = 0; i < strLen; i++) { + if (charAt(myLen-strLen+i)!=string.charAt(i)) { + return false; + } + } + return true; + } + return false; + } + + /** + * Take documentRegion at the end of + */ + public DocumentRegion textAtEnd(int numChars) { + numChars = Math.min(getLength(), numChars); + return new DocumentRegion(doc, end-numChars, end); + } + + public DocumentRegion textAtStart(int numChars) { + numChars = Math.min(getLength(), numChars); + return new DocumentRegion(doc, start, start + numChars); + } + + /** + * Fetch text between two offsets. Doesn't throw BadLocationException. + * If either one or both of the offsets points outside the + * document then they will be adjusted to point the appropriate boundary to + * retrieve the text just upto the end or beginning of the document instead. + */ + private static String textBetween(IDocument doc, int start, int end) { + Assert.isLegal(start<=end); + if (start>=doc.getLength()) { + return ""; + } + if (start<0) { + start = 0; + } + if (end>doc.getLength()) { + end = doc.getLength(); + } + if (end= 0 && doc.getChar(start) != '<'; start--) { + // nothing + } + } catch (BadLocationException e) { + // ignore + } + return new DocumentRegion(doc, start, end); + } + + public DocumentRegion textBeforeOnLine() { + try { + int line = doc.getLineOfOffset(start); + int lineOffset = doc.getLineOffset(line); + return new DocumentRegion(doc, lineOffset, start); + } catch (BadLocationException e) { + throw ExceptionUtil.unchecked(e); + } + } + + public boolean isWhiteSpace() { + for (int i = 0; i < getLength(); i++) { + if (!Character.isWhitespace(charAt(i))) { + return false; + } + } + return true; + } + + public DocumentRegion textAfterOnLine() { + DocumentRegion textAfter = this.textAfter(Integer.MAX_VALUE); + return textAfter.takeWhile(c -> c != '\n' && c != 0); + } + + public DocumentRegion takeWhile(Predicate condition) { + int i = 0; + for (; i < getLength() && condition.test(charAt(i)); i++) { + } + return this.textAtStart(i); + } + +// public Range asRange() throws BadLocationException { +// return doc.toRange(asRegion()); +// } +} \ No newline at end of file diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/FileUtil.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/FileUtil.java new file mode 100644 index 000000000..92de67c7e --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/FileUtil.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2012, 2018 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.util; + +import java.io.File; +import java.io.IOException; + +/** + * @author Steffen Pingel + * @author Leo Dos Santos + * @author Christian Dupuis + * @author Kris De Volder + */ +public class FileUtil { + + /** + * Creates a temporary folder that gets deleted on VM shutdown. WARNING: + * any files created in this temporary folder will also be deleted on shutdown. + * It also appends a timestamp to the temporary directory name. + * + * @param name + * @return + * @throws IOException + */ + public static File createTempDirectoryWithTimestamp(String name) throws IOException { + return createTempDirectory(name, Long.toString(System.nanoTime())); + } + + /** + * Creates a temporary folder that gets deleted on VM shutdown. WARNING: + * any files created in this temporary folder will also be deleted on shutdown. + * + * @param name + * @return + * @throws IOException + */ + public static File createTempDirectory(String name) throws IOException { + return createTempDirectory(name, null); + } + + + /** + * Creates a temporary folder that gets deleted on VM shutdown. WARNING: + * any files created in this temporary folder will also be deleted on shutdown. + * + * @param name + * @return + * @throws IOException + */ + public static File createTempDirectory(String name, String suffix) throws IOException { + File tempFolder = File.createTempFile(name, suffix); + if (!tempFolder.delete()) { + throw new IOException("Could not delete temp file: " + tempFolder.getAbsolutePath()); + } + if (!tempFolder.mkdirs()) { + throw new IOException("Could not create temp directory: " + tempFolder.getAbsolutePath()); + } + deleteOnShutdown(tempFolder); + return tempFolder; + } + + public static File createTempDirectory() throws IOException { + return createTempDirectoryWithTimestamp("temp"); + } + + public static boolean isJarFile(File jarFile) { + try { + return jarFile!=null && jarFile.isFile() && jarFile.toString().toLowerCase().endsWith(".jar"); + } catch (Throwable e) { + org.springsource.ide.eclipse.commons.livexp.util.Log.log(e); + return false; + } + } + + private static void deleteOnShutdown(File tempFolder) { + try { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + deleteResource(tempFolder); + })); + } catch (IllegalArgumentException | IllegalStateException | SecurityException e) { + org.springsource.ide.eclipse.commons.livexp.util.Log.log(e); + } + } + + private static void deleteResource(File file) { + if (file == null || !file.exists()) { + return; + } + + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File content : files) { + deleteResource(content); + } + } + } + file.delete(); + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/Gtk3Check.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/Gtk3Check.java new file mode 100644 index 000000000..08437cb5b --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/Gtk3Check.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2014 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Kris De Volder - Initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.util; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.Vector; + +import org.eclipse.core.runtime.Platform; +import org.osgi.framework.Bundle; +import org.osgi.framework.wiring.BundleWiring; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; + +public class Gtk3Check { + + //Related eclipse bug: + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=434869 + //If this bug is fixed before eclipse 4.4 release we can remove this code + //and any references to it + + public static final boolean isGTK3 = isGTK3(); + + /** + * Try to determine if GTK3 native libraries are loaded. If it can't be determined conclusively + * assume 'the worst' case and return true. + */ + private static boolean isGTK3() { + + if (!"linux".equals(Platform.getOS())) { + return false; + } + try { + //Note don't look in the 'swt.gtk' bundle because it is a fragment. + // Should look in the 'host' bundle instead. + Bundle bundle = Platform.getBundle("org.eclipse.swt"); + BundleWiring bw = bundle.adapt(BundleWiring.class); + ClassLoader cl = bw.getClassLoader(); + List libs = getNativeLibs(cl); + for (String lib : libs) { +// System.out.println(lib); + //TODO: not sure how good this check is. Could library exist under a different name? + if (lib.contains("swt-pi3-gtk")) { + return true; + } + } + //No problems determining loaded libs, and gtk3 lib not found! + return false; + } catch (Throwable e) { + FrameworkCoreActivator.log(e); + //Something went wrong determining if GTK3 is present. + //We must assume the worst to avoid potential JVM crash. + return true; + } + } + + @SuppressWarnings("unchecked") + private static Vector getNativeLibs(ClassLoader classLoader) throws Exception { + //See: https://stackoverflow.com/questions/1007861/how-do-i-get-a-list-of-jni-libraries-which-are-loaded + Field field = ClassLoader.class.getDeclaredField("loadedLibraryNames"); + field.setAccessible(true); + return (Vector)field.get(classLoader); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/IOUtil.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/IOUtil.java new file mode 100644 index 000000000..3afcceb12 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/IOUtil.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.util; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class IOUtil { + + /** + * Copy data from an inputstream into a file until end of the inputstream + * is reached. + *

    + * The input stream is closed automatically. + */ + public static void pipe(InputStream data, File target) throws IOException { + target.getParentFile().mkdirs(); //try to create dirs for parent if they don't exist. + OutputStream out = new BufferedOutputStream(new FileOutputStream(target)); + try { + pipe(data, out); + } finally { + out.close(); + } + } + + /** + * Copy input stream to output stream until end of the inputstream is reached. + * The intpustream is closed automatically, but the output stream is not. + */ + public static void pipe(InputStream input, OutputStream output) throws IOException { + try { + byte[] buf = new byte[1024*4]; + int n = input.read(buf); + while (n >= 0) { + output.write(buf, 0, n); + n = input.read(buf); + } + output.flush(); + } finally { + input.close(); + } + } + + public static String toString(InputStream input) throws Exception { + return toString(input, "UTF8"); + } + + public static String toString(InputStream input, String encoding) throws Exception { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + pipe(input, buf); + return buf.toString(encoding); + } + + /** + * Sick and tired of writing try-catch around close calls... If something can't close, it usually means it + * was already closed, no longer exists etc. This method catches and ignores the exceptions. + */ + public static void close(Closeable closeable) { + try { + closeable.close(); + } catch (IOException e) { + //ignore + } + } + + public static byte[] toBytes(InputStream stream) throws IOException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + pipe(stream, bytes); + return bytes.toByteArray(); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/JavaTypeUtil.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/JavaTypeUtil.java new file mode 100644 index 000000000..8d7ce9a8f --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/JavaTypeUtil.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2019 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.util; + +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Set; + +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.jdt.core.IJavaProject; +import org.springsource.ide.eclipse.commons.livexp.util.Log; + +public class JavaTypeUtil { + + public static String getFQTypeName(IJavaProject jp, String toParse) throws Exception { + String fQName = fromOutputLocation(jp, toParse); + return fQName; + } + + private static String fromOutputLocation(IJavaProject jp, String toParse) throws Exception { + if (jp.getOutputLocation() != null) { + Set outputFolders = getOutputFolders(jp); + if (outputFolders != null) { + for (IContainer iContainer : outputFolders) { + String parseTypeFromPath = parseTypeFromPath(iContainer.getRawLocation(), toParse); + if (parseTypeFromPath != null) { + return parseTypeFromPath; + } + } + } + } + return null; + } + + /** + * Will parse the type from a Bean resource path, given a base path for a project (e.g a project's output folder). + * This will attempt to find a common root between the resource path and the base path, and relativize the remaining part + * which is assumed to be the type. + * @param base path + * @param resourcePath + * @return parsed type, or null if nothing parsed + */ + private static String parseTypeFromPath(IPath base, String resourcePath) { + if (base != null) { + String baseVal = base.toOSString(); + if (baseVal != null) { + java.nio.file.Path basePath = Paths.get(baseVal); + java.nio.file.Path pathContainingType = Paths.get(resourcePath); + if (pathContainingType.startsWith(basePath)) { + try { + java.nio.file.Path relativize = basePath.relativize(pathContainingType); + return relativize.toString(); + } catch (Exception e) { + //Ignore, it means outputPath and resource path do not share common root + } + } + } + } + return null; + } + + public static Set getOutputFolders(IJavaProject jp) { + IContainer defaultOutput = getDefaultOutputFolder(jp); + if (defaultOutput!=null) { + return Collections.singleton(getDefaultOutputFolder(jp)); + } else { + return Collections.emptySet(); + } + //TODO: other output folders (i.e indivudla source folders can specifiy separate output folders) + } + + public static IContainer getDefaultOutputFolder(IJavaProject jp) { + try { + IPath loc = jp.getOutputLocation(); + String pname = loc.segment(0); + if (loc.segmentCount()==1) { + //project is its own output folder. Discouraged... but possible + return ResourcesPlugin.getWorkspace().getRoot().getProject(pname); + } else { + return ResourcesPlugin.getWorkspace().getRoot().getProject(pname).getFolder(loc.removeFirstSegments(1)); + } + } catch (Exception e) { + Log.log(e); + } + return null; + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/JobUtil.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/JobUtil.java new file mode 100644 index 000000000..88d0b97a6 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/JobUtil.java @@ -0,0 +1,208 @@ +/******************************************************************************* + * Copyright (c) 2012, 2016 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.util; + +import java.lang.reflect.InvocationTargetException; +import java.time.Duration; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeoutException; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.operation.IRunnableContext; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.progress.UIJob; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; +import org.springsource.ide.eclipse.commons.livexp.util.Log; + +public class JobUtil { + + public interface JobBody { + void run(IProgressMonitor mon) throws Exception; + } + + public static T interruptAfter(Duration waitFor, Callable body) throws Exception { + Thread self = Thread.currentThread(); + Job job = new Job("Interupter") { + @Override + protected IStatus run(IProgressMonitor arg0) { + self.interrupt(); + return Status.OK_STATUS; + } + }; + job.setSystem(true); + job.schedule(waitFor.toMillis()); + try { + return body.call(); + } catch (InterruptedException e) { + Log.log(e); + throw new TimeoutException(); + } finally { + job.cancel(); + } + } + + /** + * Should ideally not be used except for testing. Eclipse UI should provide a runnable context like + * a progress service via the workbench. + */ + public static final IRunnableContext DEFAULT_BACKGROUND_RUNNABLE_CONTEXT = new IRunnableContext() { + + @Override + public void run(boolean arg0, boolean arg1, final IRunnableWithProgress runnableWithProgress) + throws InvocationTargetException, InterruptedException { + if (runnableWithProgress != null) { + Job job = new Job("Running a background job.") { + + @Override + public IStatus run(IProgressMonitor monitor) { + try { + runnableWithProgress.run(monitor); + } catch (InvocationTargetException | InterruptedException e) { + return UIJob.errorStatus(e); + } + return Status.OK_STATUS; + } + }; + job.setSystem(true); + job.schedule(); + + } + } + }; + + /** + * Create a scheduling rule that conflicts only with itself and only + * contains itself. Jobs that want to have a 'light' impact on blocking + * other jobs but still some guarantee that they won't trample over other + * things that require access to some internal shared resource that only + * they can access should use this rule to protect the resource. + */ + public static ISchedulingRule lightRule(final String name) { + return new ISchedulingRule() { + public boolean contains(ISchedulingRule rule) { + return rule == this; + } + + public boolean isConflicting(ISchedulingRule rule) { + return rule == this || rule.contains(this); + } + + public String toString() { + return name; + }; + }; + } + + /** + * Runs a job in the background through a progress service that provides UI + * progress. Because the progress service provides UI progress, the initial + * launching of the services is done from the UI thread BEFORE the + * background job is started . A progress service is required and cannot be + * null. + * + */ + public static void runBackgroundJobWithUIProgress(final IRunnableWithProgress runnableWithProgress, + final IRunnableContext progressService, final String jobLabel) throws Exception { + // This outer runnable launches the background job + final IRunnableWithProgress outerRunnable = new IRunnableWithProgress() { + public void run(final IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + monitor.beginTask(jobLabel, IProgressMonitor.UNKNOWN); + // Fork outside UI thread + Job job = new Job(jobLabel) { + @Override + public IStatus run(IProgressMonitor monitor) { + SubMonitor subMonitor = SubMonitor.convert(monitor); + subMonitor.setTaskName(jobLabel); + try { + runnableWithProgress.run(subMonitor); + } catch (Throwable e) { + FrameworkCoreActivator.log(e); + } finally { + subMonitor.done(); + } + return Status.OK_STATUS; + } + + }; + job.schedule(); + } + }; + + // Progress services needs to be launched in UI thread. + Exception[] error = new Exception[1]; + Display.getDefault().syncExec(() -> { + try { + progressService.run(true, true, outerRunnable); + } catch (InvocationTargetException e) { + error[0] = e; + } catch (InterruptedException e) { + error[0] = e; + } + }); + + if (error[0] != null) { + throw error[0]; + } + } + + public static CompletableFuture runQuietlyInJob(String jobName, JobBody doit) { + CompletableFuture f = new CompletableFuture<>(); + Job job = new Job(jobName) { + + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + doit.run(monitor); + f.complete(null); + } catch (Throwable e) { + f.completeExceptionally(e); + } finally { + monitor.done(); + } + return Status.OK_STATUS; + } + + }; + job.setSystem(true); + job.schedule(); + return f; + } + + public static CompletableFuture runInJob(String jobName, JobBody doit) { + CompletableFuture f = new CompletableFuture<>(); + Job job = new Job(jobName) { + + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + doit.run(monitor); + f.complete(null); + } catch (Throwable e) { + f.completeExceptionally(e); + } finally { + monitor.done(); + } + return Status.OK_STATUS; + } + + }; + job.schedule(); + return f; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/StringUtils.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/StringUtils.java new file mode 100644 index 000000000..332b08c34 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/StringUtils.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2018 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.StringTokenizer; + +public class StringUtils { + + public static boolean hasText(String val) { + return val!=null && !val.trim().isEmpty(); + } + + /** + * Convert a {@code String} array into a delimited {@code String} (e.g. CSV). + *

    Useful for {@code toString()} implementations. + * @param arr the array to display (potentially {@code null} or empty) + * @param delim the delimiter to use (typically a ",") + * @return the delimited {@code String} + */ + public static String arrayToDelimitedString(Object[] arr, String delim) { + if (arr==null || arr.length==0) { + return ""; + } + StringBuilder buf = new StringBuilder(); + boolean first = true; + for (Object object : arr) { + if (!first) { + buf.append(delim); + } + buf.append(String.valueOf(object)); + first = false; + } + return buf.toString(); + } + + /** + * Check that the given {@code CharSequence} is neither {@code null} nor + * of length 0. + *

    Note: this method returns {@code true} for a {@code CharSequence} + * that purely consists of whitespace. + *

    +	 * StringUtils.hasLength(null) = false
    +	 * StringUtils.hasLength("") = false
    +	 * StringUtils.hasLength(" ") = true
    +	 * StringUtils.hasLength("Hello") = true
    +	 * 
    + * @param str the {@code CharSequence} to check (may be {@code null}) + * @return {@code true} if the {@code CharSequence} is not {@code null} and has length + * @see #hasText(String) + */ + public static boolean hasLength(CharSequence str) { + return (str != null && str.length() > 0); + } + + /** + * Tokenize the given {@code String} into a {@code String} array via a + * {@link StringTokenizer}. + *

    Trims tokens and omits empty tokens. + *

    The given {@code delimiters} string can consist of any number of + * delimiter characters. Each of those characters can be used to separate + * tokens. A delimiter is always a single character; for multi-character + * delimiters, consider using {@link #delimitedListToStringArray}. + * @param str the {@code String} to tokenize (potentially {@code null} or empty) + * @param delimiters the delimiter characters, assembled as a {@code String} + * (each of the characters is individually considered as a delimiter) + * @return an array of the tokens + * @see java.util.StringTokenizer + * @see String#trim() + * @see #delimitedListToStringArray + */ + public static String[] tokenizeToStringArray(String str, String delimiters) { + return tokenizeToStringArray(str, delimiters, true, true); + } + + /** + * Tokenize the given {@code String} into a {@code String} array via a + * {@link StringTokenizer}. + *

    The given {@code delimiters} string can consist of any number of + * delimiter characters. Each of those characters can be used to separate + * tokens. A delimiter is always a single character; for multi-character + * delimiters, consider using {@link #delimitedListToStringArray}. + * @param str the {@code String} to tokenize (potentially {@code null} or empty) + * @param delimiters the delimiter characters, assembled as a {@code String} + * (each of the characters is individually considered as a delimiter) + * @param trimTokens trim the tokens via {@link String#trim()} + * @param ignoreEmptyTokens omit empty tokens from the result array + * (only applies to tokens that are empty after trimming; StringTokenizer + * will not consider subsequent delimiters as token in the first place). + * @return an array of the tokens + * @see java.util.StringTokenizer + * @see String#trim() + * @see #delimitedListToStringArray + */ + public static String[] tokenizeToStringArray( + String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { + + if (str == null) { + return new String[0]; + } + + StringTokenizer st = new StringTokenizer(str, delimiters); + List tokens = new ArrayList<>(); + while (st.hasMoreTokens()) { + String token = st.nextToken(); + if (trimTokens) { + token = token.trim(); + } + if (!ignoreEmptyTokens || token.length() > 0) { + tokens.add(token); + } + } + return toStringArray(tokens); + } + + /** + * Copy the given {@link Collection} into a {@code String} array. + *

    The {@code Collection} must contain {@code String} elements only. + * @param collection the {@code Collection} to copy + * (potentially {@code null} or empty) + * @return the resulting {@code String} array + */ + public static String[] toStringArray(Collection collection) { + return (collection != null ? collection.toArray(new String[0]) : new String[0]); + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/XmlUtils.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/XmlUtils.java new file mode 100644 index 000000000..52e41d5e5 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/util/XmlUtils.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2013 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.util; + +import org.w3c.dom.Node; + +/** + * Helper methods to access pieces of XMLData in a parsed Document. + */ +public class XmlUtils { + + public static String getTagName(Node labelNode) { + if (labelNode.getNodeType()==Node.ELEMENT_NODE) { + return labelNode.getNodeName(); + } + return null; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/workspace/ClasspathListenerManager.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/workspace/ClasspathListenerManager.java new file mode 100644 index 000000000..fc17c9fd7 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/workspace/ClasspathListenerManager.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2014 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.workspace; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.ElementChangedEvent; +import org.eclipse.jdt.core.IElementChangedListener; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaElementDelta; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; +import org.springsource.ide.eclipse.commons.livexp.ui.Disposable; + +/** + * An instance of this class provides a means to register + * listeners that get notified when classpath for a IJavaProject + * changes. + * + * @author Kris De Volder + */ +public class ClasspathListenerManager implements Disposable { + + public interface ClasspathListener { + public abstract void classpathChanged(IJavaProject jp); + } + + private class MyListener implements IElementChangedListener { + + //@Override + public void elementChanged(ElementChangedEvent event) { + visit(event.getDelta()); + } + + private void visit(IJavaElementDelta delta) { + IJavaElement el = delta.getElement(); + switch (el.getElementType()) { + case IJavaElement.JAVA_MODEL: + visitChildren(delta); + break; + case IJavaElement.JAVA_PROJECT: + if (isClasspathChanged(delta.getFlags())) { + listener.classpathChanged((IJavaProject)el); + } + break; + default: + break; + } + } + + private boolean isClasspathChanged(int flags) { + return 0!= (flags & ( + IJavaElementDelta.F_CLASSPATH_CHANGED | + IJavaElementDelta.F_RESOLVED_CLASSPATH_CHANGED + )); + } + + public void visitChildren(IJavaElementDelta delta) { + for (IJavaElementDelta c : delta.getAffectedChildren()) { + visit(c); + } + } + } + + private ClasspathListener listener; + private MyListener myListener; + + /** + * @param initialEvent If true, events are fired immediately on all existing java + * projects, treating the connection of the listener itself as a change event. + * This allows clients to become aware of all classpaths from the start and + * continually monitor them for changes from that point onward. + */ + public ClasspathListenerManager(ClasspathListener listener, boolean initialEvent) { + this.listener = listener; + if (initialEvent) { + for (IProject p : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { + try { + if (p.isAccessible() && p.hasNature(JavaCore.NATURE_ID)) { + IJavaProject jp = JavaCore.create(p); + listener.classpathChanged(jp); + } + } catch (CoreException e) { + FrameworkCoreActivator.log(e); + } + } + } + JavaCore.addElementChangedListener(myListener=new MyListener(), ElementChangedEvent.POST_CHANGE); + } + + public ClasspathListenerManager(ClasspathListener listener) { + this(listener, false); + } + + public void dispose() { + if (myListener!=null) { + JavaCore.removeElementChangedListener(myListener); + myListener = null; + } + } + +} \ No newline at end of file diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/workspace/ProjectChangeListenerManager.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/workspace/ProjectChangeListenerManager.java new file mode 100644 index 000000000..f22ccef7d --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/workspace/ProjectChangeListenerManager.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2015 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.workspace; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IWorkspace; +import org.eclipse.core.runtime.OperationCanceledException; + +/** + * @author Kris De Volder + */ +public class ProjectChangeListenerManager implements IResourceChangeListener { + + public interface ProjectChangeListener { + void projectChanged(IProject project); + } + + private IWorkspace workspace; + private ProjectChangeListener listener; + + public ProjectChangeListenerManager(IWorkspace workspace, ProjectChangeListener listener) { + this.workspace = workspace; + this.listener = listener; + this.workspace.addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); + } + + @Override + public void resourceChanged(IResourceChangeEvent event) { + try { + // Find out if a project was opened. + IResourceDelta delta = event.getDelta(); + if (delta == null) return; + + IResourceDelta[] projDeltas = delta.getAffectedChildren( + IResourceDelta.CHANGED| + IResourceDelta.ADDED| + IResourceDelta.REMOVED + ); + for (int i = 0; i < projDeltas.length; ++i) { + IResource resource = projDeltas[i].getResource(); + if (!(resource instanceof IProject)) + continue; + + IProject project = (IProject) resource; + projectChanged(project); + } + } catch (OperationCanceledException oce) { + // do nothing + } + } + + private void projectChanged(final IProject project) { + listener.projectChanged(project); + } + + public void dispose() { + workspace.removeResourceChangeListener(this); + if (listener!=null) { + listener = null; + } + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/workspace/ProjectDeletionListenerManager.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/workspace/ProjectDeletionListenerManager.java new file mode 100644 index 000000000..cd92edfbf --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.core/src/org/springsource/ide/eclipse/commons/frameworks/core/workspace/ProjectDeletionListenerManager.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2015 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.core.workspace; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IWorkspace; + +/** + * An adapter/manager for a single listener interested in project deletions + * from the Eclipse workspace. + *

    + * Create an instance of ProjectDeletionListenerManager and pass it a + * ProjectDeletionListener to attach it to the workspace. + *

    + * To deregister the listener, dispose the manager. + * + * @author Kris De Volder + */ +public class ProjectDeletionListenerManager implements IResourceChangeListener { + + public interface ProjectDeletionListener { + void projectWasDeleted(IProject project); + } + + private IWorkspace workspace; + private ProjectDeletionListener listener; + + public ProjectDeletionListenerManager(IWorkspace workspace, ProjectDeletionListener listener) { + this.workspace = workspace; + this.listener = listener; + this.workspace.addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE); + } + + @Override + public void resourceChanged(IResourceChangeEvent event) { + IResourceDelta _delta = event.getDelta(); + if (_delta!=null) { + IResourceDelta[] children = _delta.getAffectedChildren(); + if (children!=null) { + for (IResourceDelta delta : children) { + if (delta!=null) { + IResource rsrc = delta.getResource(); + if (rsrc instanceof IProject) { + int kind = delta.getKind(); + if (kind==IResourceDelta.REMOVED) { + if (!isRename(delta)) { + listener.projectWasDeleted((IProject) rsrc); + } + } + } + } + } + } + } + } + + private boolean isRename(IResourceDelta delta) { + return 0!=(delta.getFlags()&IResourceDelta.MOVED_TO); + } + + public void dispose() { + if (listener!=null) { + workspace.removeResourceChangeListener(this); + listener = null; + } + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/.classpath b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/.classpath new file mode 100644 index 000000000..eca7bdba8 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/.project b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/.project new file mode 100644 index 000000000..6ae437451 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/.project @@ -0,0 +1,28 @@ + + + org.springsource.ide.eclipse.commons.frameworks.test.util + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/.settings/org.eclipse.jdt.core.prefs b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..0c68a61dc --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/META-INF/MANIFEST.MF b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/META-INF/MANIFEST.MF new file mode 100644 index 000000000..ac70dfc5a --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/META-INF/MANIFEST.MF @@ -0,0 +1,13 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: com.springsource.sts.frameworks.test.util +Bundle-SymbolicName: org.springsource.ide.eclipse.commons.frameworks.test.util;singleton:=true +Bundle-Version: 4.8.1.qualifier +Bundle-ActivationPolicy: lazy +Bundle-Vendor: Pivotal Software, Inc. +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Require-Bundle: org.eclipse.swtbot.go, + org.springsource.ide.eclipse.commons.tests.util, + org.eclipse.debug.core, + org.hamcrest.library +Export-Package: org.springsource.ide.eclipse.commons.frameworks.test.util diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/README.txt b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/README.txt new file mode 100644 index 000000000..d7388b004 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/README.txt @@ -0,0 +1 @@ +This plugin contains common testing infrastructure and utilities for Grails and Roo tests. \ No newline at end of file diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/about.html b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/about.html new file mode 100644 index 000000000..ae88621cc --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/about.html @@ -0,0 +1,22 @@ + + + + +About Eclipse Integration Commons + + + +

    About Eclipse Integration Commons

    +

    June 30, 2015

    +

    Abstract

    +

    The Eclipse Integration Commons project provides components that help +other projects to implement Eclipse-based tooling.

    +

    License

    +

    Pivotal Software, Inc. (“Pivotal”) makes available all content +in this download ("Content"). Unless otherwise indicated below, the +Content is provided to you under the terms and conditions of the Eclipse +Public License Version 1.0 ("EPL"). A copy of the EPL is available in +the file called license.txt. For purposes of the EPL, "Program" will +mean the Content.

    + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/build.properties b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/build.properties new file mode 100644 index 000000000..b47227947 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/build.properties @@ -0,0 +1,7 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + about.html + +jars.extra.classpath = platform:/plugin/org.eclipse.swtbot.go diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/pom.xml b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/pom.xml new file mode 100644 index 000000000..5355b81c9 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + org.springframework.boot.ide + org.springframework.boot.ide.servers + 4.8.1-SNAPSHOT + ../pom.xml + + + org.springsource.ide.eclipse.commons.frameworks.test.util + eclipse-plugin + org.springsource.ide.eclipse.commons.frameworks.test.util + + + + + org.eclipse.tycho + tycho-p2-plugin + ${tycho-version} + + + second-generate-p2-metadata + + p2-metadata + + verify + + + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/ACondition.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/ACondition.java new file mode 100644 index 000000000..86dd3e513 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/ACondition.java @@ -0,0 +1,250 @@ +/******************************************************************************* + * Copyright (c) 2012, 2018 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +import static org.junit.Assert.fail; + +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.core.runtime.jobs.IJobManager; +import org.eclipse.core.runtime.jobs.Job; +import org.junit.Assert; +import org.springsource.ide.eclipse.commons.tests.util.StsTestUtil; + +import junit.framework.AssertionFailedError; + + +/** + * Abstract class to create a "waitable" condition. This is to be used in implementing + * tests where we want some condition to be true eventually, but cannot be sure it will be + * true immediately. + *

    + * Typical use would like so: + * + * new ACondition() { + * public boolean test() throws Exception { + * ... some asserts ... + * return ...something to test...; + * } + * }.waitFor(4000); // wait for 4 seconds or until test passes. + * + * An exception will be thrown if the test method does not return true before the + * timeout limit. + *

    + * If the test method throws an exception this is treated the same as returning false. + * If condition fails, we will try to rethrow a pertinent exception (typically the + * exception thrown by the test method, the last time we tried to run it. + * + * @author Kris De Volder + */ +public abstract class ACondition { + + private String description = null; + + /** + * This method is deprecated because it is a common problem that client calls it + * and then forgets to call the waitFor method on the created instance. This results + * in test code that looks like it is testing something but doesn't because the + * asserts inside the ACondition are never actually executed. + *

    + * Clients should instead call one of the constructor that takes a timeout + * as a parameter (which does the to waitFor automatically). + */ + @Deprecated + public ACondition() { + } + + /** + * This method is deprecated because it is a common problem that client calls it + * and then forgets to call the waitFor method on the created instance. This results + * in test code that looks like it is testing something but doesn't because the + * asserts inside the ACondition are never actually executed. + *

    + * Clients should instead call one of the constructor that takes a timeout + * as a parameter (which does the to waitFor automatically). + */ + @Deprecated + public ACondition(String description) { + this.description = description; + } + + public ACondition(long timeout) throws Exception { + this(); + waitFor(timeout); + } + + public ACondition(String description, long timeout) throws Exception { + this(description); + waitFor(timeout); + } + + Throwable e = null; + + /** + * Deprecated. Use one of the constructors that accepts a timeout value and automatically + * calls waitFor. + */ + @Deprecated + public void waitFor(long timeout) throws Exception { + long SLEEP_FOR = Math.min(2000, timeout/5); + long startTime = System.currentTimeMillis(); + long endTime = startTime + timeout; + boolean result = false; + StsTestUtil.waitForDisplay(); //Make sure this gets called at least once to avoid 'UI thread starvation'. + while (!(result = doTest()) && System.currentTimeMillis() < endTime) { + StsTestUtil.waitForDisplay(); // Avoids UI deadlock by allowing UI to process events. + try { + Thread.sleep(SLEEP_FOR); + } catch (InterruptedException e) { + } + } + if (!result) { + //Try our best to create a 'nice' exception reflecting the reason for the test failure + System.err.println("ACondition "+describe()+" timed out. Dumping current Thread stacks...\n" + + StsTestUtil.getStackDumps() + ); + if (e!=null) { + if (e instanceof Exception) { + throw (Exception)e; + } else if (e instanceof Error) { + throw (Error)e; + } else { + throw new RuntimeException(e); + } + } else { + throw new RuntimeException(getMessage()); + } + } + if (description!=null) { + System.out.println(description + " succeeded after: " + (System.currentTimeMillis() - startTime)); + } + } + + private String describe() { + if (description!=null) { + return "["+description+"]"; + } + return ""; + } + + private boolean doTest() { + boolean result = false; + try { + e = null; + result = test(); + } catch (Throwable e) { + this.e = e; + } + return result; + } + + /** + * Test something. If the method returns true, the test passes. + * If it returns false or throws an exception the test fails (and will be + * retried until it passes or timeout is reached). + */ + public abstract boolean test() throws Exception; + + /** + * Message used when time out reached without an exception + */ + public String getMessage() { + return "timed out"; + } + + public static void assertJobManagerIdle() { + final IJobManager jm = Job.getJobManager(); + if (jm.isIdle()) { + return; //OK! + } + //Make a nice message listing all the jobs and their present state. + Job[] allJobs = jm.find(null); + StringBuffer msg = new StringBuffer("JobManager not idle: \n"); + for (Job job : allJobs) { + msg.append(" Job: "+job.getName() + " State: " + stateString(job) +"\n"); + } + throw new AssertionFailedError(msg.toString()); + } + + public static String stateString(Job job) { + int state = job.getState(); + switch (state) { + case Job.RUNNING: + return "RUNNING"; + case Job.SLEEPING: + return "SLEEPING"; + case Job.WAITING: + return "WAITING"; + case Job.NONE: + return "NONE"; + default: + return ""+state; + } + } + + /** + * A Java 8 friendlier way of using this class with a lambda. + *

    + * Waits for some asserts to pass by periodically checking them until either: + * a) the asserts all pass (i.e. asserter returns without throwing); or b) + * the timeout is exceeded. + */ + public static void waitFor(String name, long timeout, final Asserter asserter) throws Exception { + new ACondition("Wait for "+name, timeout) { + @Override + public boolean test() throws Exception { + asserter.execute(); + return true; + } + }; + } + + /** + * Repeatedly check asserter for a given duration. If the asserter ever fails + * during that time this check fails. + */ + public static void expectAllways(String name, long timeout, final Asserter asserter) throws Exception { + AtomicReference firstFail = new AtomicReference<>(); + try { + waitFor(name, timeout, () -> { + boolean success = false; + try { + asserter.execute(); + success = true; + } catch (Throwable e) { + firstFail.set(e); + } finally { + //First time asserter does not succeed... the 'waitFor' should end. + Assert.assertFalse(success); + } + }); + //The waitfor above should fail. Reaching here means it passed. + // Make sure we have some kind of exception. Create one if need be. + if (firstFail.get()==null) { + firstFail.set(new AssertionFailedError(name)); + } + } catch (Throwable e) { + //expected. The wait for should fail with negated asserter + } + Throwable e = firstFail.get(); + if (e!=null) { + if (e instanceof Exception) { + throw (Exception)e; + } else if (e instanceof Error) { + throw (Error)e; + } else { + throw new RuntimeException(e); + } + } + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/AbstractedWaitCondition.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/AbstractedWaitCondition.java new file mode 100644 index 000000000..f4c2b2162 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/AbstractedWaitCondition.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +import org.eclipse.swtbot.swt.finder.SWTBot; +import org.eclipse.swtbot.swt.finder.waits.ICondition; + +/** + * @author Kris De Volder + */ +public abstract class AbstractedWaitCondition implements ICondition { + protected SWTBot bot; + + protected AbstractedWaitCondition(SWTBot bot) { + this.bot = bot; + } + + public void init(SWTBot bot) { + // + } + + public void waitForTest() { + bot.waitUntil(this); + } + + public void waitForTest(long timeout) { + bot.waitUntil(this, timeout); + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/Asserter.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/Asserter.java new file mode 100644 index 000000000..008666ca3 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/Asserter.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +/** + * An {@link Asserter} provides a single method which is expected + * to check some stuff during a test (i.e. typically execute a bunch + * of asserts). + * + * @author Kris De Volder + */ +@FunctionalInterface +public interface Asserter { + /** + * Called on to check some conditions during a test. Should return + * normally to indicate 'success' and throw an exception of any type + * to indicate 'failure'. + */ + void execute() throws Exception; +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/Asserter1.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/Asserter1.java new file mode 100644 index 000000000..b975ee19c --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/Asserter1.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +/** + * An {@link Asserter1} provides a single method which is expected + * to check some stuff during a test (i.e. typically execute a bunch + * of asserts). + *

    + * Note: This is a variant of {@link Asserter} which takes a single + * parameter (instead of none). + * + * @author Kris De Volder + */ +@FunctionalInterface +public interface Asserter1 { + + /** + * Called on to check some conditions during a test. Should return + * normally to indicate 'success' and throw an exception of any type + * to indicate 'failure'. + */ + void execute(T testSubject) throws Exception; + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/ExternalCommand.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/ExternalCommand.java new file mode 100644 index 000000000..3ab0a7c8b --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/ExternalCommand.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; + +/** + * Encapsulates information about an 'external' command that can be run through the OS. + *

    + * This is a simplistic implementation. A more sophisticate implementation should allow for + * different OS's (commands may return different information depending on the OS). + * + * @author Kris De Volder + */ +public class ExternalCommand { + + private final String[] command; + + public ExternalCommand(String... command) { + ArrayList pieces = new ArrayList(command.length); + for (String piece : command) { + if (piece!=null) { + pieces.add(piece); + } + } + this.command = pieces.toArray(new String[pieces.size()]); + } + + public String[] getProgramAndArgs() { + return command; + } + + @Override + public String toString() { + StringBuffer buf = new StringBuffer(); + boolean first = true; + for (String piece : command) { + if (!first) { + buf.append(" "); + } + buf.append(piece); + first = false; + } + return buf.toString(); + } + + /** + * Just before executing the command with a ProcessBuilder instance, this method is called, + * giving the command a chance to apply some extra configuration (e.g. set some environment + * parameters). + */ + public void configure(ProcessBuilder processBuilder) { + //Default implementation does nothing. Subclasses may override. + } + + /** + * A convenient way to execute commands suitable for use in tests. The output and + * result of commands are logged to the console and if the command returns non + * 0 exit value an exception is thrown. + */ + public void exec(File workdir) throws IOException, InterruptedException { + System.out.println(">>> exec: "+this); + ExternalProcess process = new ExternalProcess(workdir, this); + System.out.println(process); + org.junit.Assert.assertEquals(0, process.getExitValue()); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/ExternalProcess.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/ExternalProcess.java new file mode 100644 index 000000000..f0667a551 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/ExternalProcess.java @@ -0,0 +1,182 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; + +import org.junit.Assert; + + +/** + * Convenient wrapper around a {@link Process}. Simplifies the synchronous execution of external + * commands by handling reading from out and error streams and either buffering the result output + * for later retrieval, or forwarding the to output to designated streams. + * + * @author Kris De Volder + */ +public class ExternalProcess { + + /** + * A thread that keeps reading input from a Stream until the end is reached + * or there's some error reading the Stream. + */ + public static class StreamGobler extends Thread { + + private final OutputStream echo; + private InputStream toRead; //Stream to read. This is nulled after all input has been consumed. + + /** + * Creates a StreamGobler that reads input from an input stream + * and buffers up all input it has read for later retrieval via + * the getOut() method. + */ + public StreamGobler(InputStream toRead) { + this(toRead, new ByteArrayOutputStream()); + } + + /** + * Creates a StreamGobler that reads input from an input stream + * and writes it out to an outputstream. + */ + public StreamGobler(InputStream toRead, OutputStream forwardTo) { + this.toRead = toRead; + this.echo = forwardTo; + start(); + } + + @Override + public void run() { + byte[] buf = new byte[256]; + while (toRead!=null) { + try { + int i = toRead.read(buf); + if (i==-1) { + //EOF + toRead = null; //Done! + } else { + append(buf, i); + } + } catch (IOException e) { + toRead = null; + ByteArrayOutputStream errMsg = new ByteArrayOutputStream(); + e.printStackTrace(new PrintStream(errMsg)); + append(errMsg.toByteArray()); + } + } + } + + private void append(byte[] buf) { + append(buf, buf.length); + } + + private void append(byte[] buf, int len) { + if (echo!=null) { + try { + echo.write(buf, 0, len); + } catch (IOException e) { + } + } + } + + public String getContents() throws InterruptedException { + try { + this.join(); + if (echo instanceof ByteArrayOutputStream) { + return ((ByteArrayOutputStream)echo).toString(); + } else { + return null; + } + } finally { + toRead = null; + } + } + } + + private Process process; + private StreamGobler err; // Standard error is to be read from here + private StreamGobler out; // Standard out is to be read from here + private int exitValue = -9999; + private ExternalCommand cmd; + + /** + * Creates an external process and waits for it to terminate. The output and error streams + * will be read and forwarded to System.out and System.err + */ + public ExternalProcess(File workingDir, ExternalCommand cmd) throws IOException, InterruptedException { + this(workingDir, cmd, false); + } + + private void init(File workingDir, ExternalCommand cmd, + OutputStream outStream, OutputStream errStream) throws IOException, + InterruptedException { + this.cmd = cmd; + ProcessBuilder processBuilder = new ProcessBuilder(cmd.getProgramAndArgs()); + processBuilder.directory(workingDir); + cmd.configure(processBuilder); + process = processBuilder.start(); + err = new StreamGobler(process.getErrorStream(), errStream); + out = new StreamGobler(process.getInputStream(), outStream); + exitValue = process.waitFor(); + } + + public ExternalProcess(File workingDir, ExternalCommand cmd, boolean captureStreams) throws IOException, InterruptedException { + if (captureStreams) { + init(workingDir, cmd, new ByteArrayOutputStream(), new ByteArrayOutputStream()); + } else { + init(workingDir, cmd, System.out, System.err); + } + } + + public String getOut() throws InterruptedException { + return out.getContents(); + } + + public String getErr() throws InterruptedException { + return err.getContents(); + } + + @Override + public String toString() { + StringBuffer result = new StringBuffer(); + try { + process.exitValue(); + result.append(">>>> ExternalProcess: "); + result.append(cmd+"\n"); + result.append("exitValue = "+exitValue+"\n"); + String strOut = getOut(); + if (strOut!=null) { + result.append("\n------- System.out -------\n"); + result.append(strOut); + } + String strErr = getErr(); + if (strErr!=null) { + result.append("\n------- System.err -------\n"); + result.append(getOut()); + } + result.append("<<<< ExternalProcess"); + return result.toString(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return result.toString(); + } catch (IllegalThreadStateException e) { + return "ExternalProcess(RUNNING, "+cmd+")"; + } + } + + public int getExitValue() { + return exitValue; + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/ResourceExists.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/ResourceExists.java new file mode 100644 index 000000000..20669e0a9 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/ResourceExists.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +import org.eclipse.core.resources.IResource; +import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; +import org.springsource.ide.eclipse.commons.tests.util.StsTestUtil; + + +/** + * An SWTBot condition that checks whether a given IResource exists in the workspace. + *

    + * A Grails create-app command is spawned by a create grails project wizard. + * We wait for this condition to ensure grails command that creates the + * project is done. + * @author Kris De Volder + */ +public class ResourceExists extends DefaultCondition { + + private IResource resource; + + /** + * Create an SWTBot condition that checks whether the resource + * pointed at by a given path String exists. + */ + public ResourceExists(String path) { + this.resource = StsTestUtil.getResource(path); + } + + public boolean test() throws Exception { + try { + boolean exists = resource.exists(); +// System.out.println("Resource '"+resource+"' exists? " + exists); + return exists; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + public String getFailureMessage() { + return "The file '"+resource+"' does not exist."; + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/SWTBotConditions.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/SWTBotConditions.java new file mode 100644 index 000000000..c0cfd15c0 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/SWTBotConditions.java @@ -0,0 +1,243 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +import java.util.List; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Widget; +import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; +import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView; +import org.eclipse.swtbot.swt.finder.SWTBot; +import org.eclipse.swtbot.swt.finder.finders.MenuFinder; +import org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory; +import org.eclipse.swtbot.swt.finder.waits.ICondition; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotStyledText; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; +import org.hamcrest.Matcher; + +/** + * @author Kris De Volder + * @author Nieraj Singh + * @author Steffen Pingel + * @created Jul 8, 2010 + */ +public class SWTBotConditions { + + public static final long PROJECT_ERROR_MARKER_TIMEOUT = 30000; + public static final long CHILD_NODE_POPULATE_TIMEOUT = 60000; + + private SWTBotConditions() { + // factor util + } + + public static org.hamcrest.Matcher withTextContaining(String text) { + return new WithTextContaining(text); + } + + public static SWTBotTreeItem waitForNodeExpanded(SWTBot bot, String nodeName) { + TreeNodeExpander expander = new TreeNodeExpander(bot); + return expander.expand(nodeName); + } + + public static List waitForShellMenuList(SWTBot bot, String name, + boolean recursive) { + return new ActiveShellMenu(bot, name, recursive).getMenus(); + } + + public static SWTBotTreeItem waitChildNodeToAppear(SWTBot bot, + final SWTBotTreeItem parentNode, final String nodeName) { + final SWTBotTreeItem[] child = new SWTBotTreeItem[1]; + bot.waitUntil(new ICondition() { + + public boolean test() throws Exception { + child[0] = parentNode.getNode(nodeName); + return child[0] != null; + } + + public void init(SWTBot bot) { + // + } + + public String getFailureMessage() { + return "Unable to find child node: " + nodeName + + " for parent node: " + parentNode.getText(); + } + + }, CHILD_NODE_POPULATE_TIMEOUT); + + return child[0]; + } + + public static void waitForProjectErrorMarkersCleared(SWTBot bot, + String projectName) { + new ProjectErrorMarkersCleared(bot, projectName) + .waitForTest(PROJECT_ERROR_MARKER_TIMEOUT); + } + + protected static class TreeNodeExpander extends AbstractedWaitCondition { + + protected TreeNodeExpander(SWTBot bot) { + super(bot); + } + + private SWTBotTreeItem nodeToExpand; + + public boolean test() throws Exception { + boolean expanded = nodeToExpand.isExpanded(); + SWTBotUtils.print("Tree item: " + nodeToExpand.getText() + + " is expanded: " + expanded); + return expanded; + } + + public String getFailureMessage() { + return "Tree Node is not expanded or populated"; + } + + public SWTBotTreeItem expand(String nodeName) { + nodeToExpand = bot.tree().expandNode(nodeName); + waitForTest(); + SWTBotUtils.print("Has expanded: " + nodeName + " in shell " + + bot.activeShell().getText()); + return nodeToExpand; + } + } + + protected static class ActiveShellMenu extends AbstractedWaitCondition { + + protected SWTBot bot; + private String name; + private boolean recursive; + private List found; + + public ActiveShellMenu(SWTBot bot, String name, boolean recursive) { + super(bot); + this.name = name; + this.recursive = recursive; + } + + protected MenuFinder getMenuFinder() { + return new MenuFinder(); + } + + public boolean test() throws Exception { + MenuFinder finder = getMenuFinder(); + SWTBotUtils.print("Getting menus for shell: " + + bot.activeShell().getText()); + SWTBotUtils + .print("Is active: " + bot.activeShell().isActive() + ""); + + Matcher menuMatcher = WidgetMatcherFactory + .withMnemonic(name); + Shell shell = bot.activeShell().widget; + found = finder.findMenus(shell, menuMatcher, recursive); + + boolean hasFound = found != null && found.size() > 0; + SWTBotUtils.print("Has found menus: " + hasFound + " for: " + name); + return hasFound; + } + + public List getMenus() { + waitForTest(); + return found; + } + + public void init(SWTBot bot) { + this.bot = bot; + } + + public String getFailureMessage() { + return "Failed to find menus for " + name + " in shell: " + + bot.activeShell().getText(); + } + } + + protected static class ProjectErrorMarkersCleared extends + AbstractedWaitCondition { + + private String projectName; + + public ProjectErrorMarkersCleared(SWTBot bot, String projectName) { + super(bot); + this.projectName = projectName; + } + + public boolean test() throws Exception { + IProject project = ResourcesPlugin.getWorkspace().getRoot() + .getProject(projectName); + int markerSeverity = project.findMaxProblemSeverity( + IMarker.PROBLEM, true, IResource.DEPTH_INFINITE); + + if (markerSeverity >= IMarker.SEVERITY_ERROR) { + SWTBotUtils.print("For project " + projectName + + " found max severity marker of " + markerSeverity + + ". Waiting for errors to clear."); + return false; + } + return true; + } + + public String getFailureMessage() { + return "Condition failed because of error markers present in project children for project: " + + projectName; + } + + } + + public static ICondition consoleContains(String string) { + return new ConsoleContains(string); + } + + public static class ConsoleContains implements ICondition { + + private SWTWorkbenchBot bot; + private String searchString; + private String msg; + + public ConsoleContains(String string) { + this.searchString = string; + } + + public boolean test() throws Exception { + msg = "Could not open Console view"; + SWTBotView console = bot.viewByTitle("Console"); + msg = "Could not find textWidget in Console view"; + SWTBotStyledText textWidget = console.bot().styledText(); + msg = "Could not get the text from the Console view"; + String text = textWidget.getText(); + msg = "Looking for: '" +searchString+"' but found \n------\n"+ text+ "\n-----"; + return text.contains(searchString); + } + + public void init(SWTBot bot) { + this.bot = (SWTWorkbenchBot) bot; + } + + public String getFailureMessage() { + return msg; + } + + } + + public static void waitForConsoleOutput(SWTWorkbenchBot bot, String string, long timeout) { + bot.waitUntil(consoleContains(string), timeout); + } + + public static void waitForConsoleOutput(SWTWorkbenchBot bot, String string) { + bot.waitUntil(consoleContains(string)); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/SWTBotUtils.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/SWTBotUtils.java new file mode 100644 index 000000000..908cf43eb --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/SWTBotUtils.java @@ -0,0 +1,520 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.widgetOfType; +import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.withRegex; +import static org.eclipse.swtbot.swt.finder.waits.Conditions.waitForShell; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.List; + +import junit.framework.Assert; + +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.model.IProcess; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Widget; +import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; +import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotPerspective; +import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView; +import org.eclipse.swtbot.swt.finder.SWTBot; +import org.eclipse.swtbot.swt.finder.finders.ContextMenuFinder; +import org.eclipse.swtbot.swt.finder.finders.MenuFinder; +import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; +import org.eclipse.swtbot.swt.finder.results.Result; +import org.eclipse.swtbot.swt.finder.results.VoidResult; +import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences; +import org.eclipse.swtbot.swt.finder.utils.SWTUtils; +import org.eclipse.swtbot.swt.finder.utils.TableCollection; +import org.eclipse.swtbot.swt.finder.utils.TableRow; +import org.eclipse.swtbot.swt.finder.waits.Conditions; +import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; +import org.eclipse.swtbot.swt.finder.waits.ICondition; +import org.eclipse.swtbot.swt.finder.waits.WaitForObjectCondition; +import org.eclipse.swtbot.swt.finder.widgets.AbstractSWTBot; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotList; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotStyledText; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTable; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; +import org.eclipse.ui.PlatformUI; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.springsource.ide.eclipse.commons.tests.util.StsTestUtil; + + +/** + * Utility methods that are useful in writing SWTBot tests. + * @author Kris De Volder + * @author Nieraj Singh + * @author Tomasz Zarna + */ +public class SWTBotUtils { + + public static final long PROCESS_COMPLETION_TIMEOUT = 60000; //Needs enough time to finish running grails command like compile -non-interactive + public static final long WAIT_FOR_BUILD_TIMEOUT = 10000; + + /** + * Open a particular perspective, or try to activate it if it can't be + * opened (maybe it already open) + */ + public static void openPerspective(SWTWorkbenchBot bot, + String perspectiveLabel) { + SWTBotShell shell = null; + try { + menu(bot, "Window").menu("Open Perspective").menu("Other...") + .click(); + + shell = bot.shell("Open Perspective"); + // SWTBotUtils.screenshot("openPerspective_"+perspectiveLabel); + assertTrue(shell.isOpen()); + bot.waitUntil(SWTBotUtils.widgetMakeActive(shell)); + shell.bot().table().select(perspectiveLabel); + + shell.bot().button("OK").click(); + bot.waitUntil(Conditions.shellCloses(shell)); + } catch (Exception e) { + if (shell != null && shell.isOpen()) + shell.close(); + System.err.println("Couldn't open perspective '" + perspectiveLabel + + "'\n" + + "trying to activate already open perspective instead"); + // maybe somehow the perspective is already opened (by another test + // before us) + SWTBotPerspective perspective = bot + .perspectiveByLabel(perspectiveLabel); + perspective.activate(); + assertTrue(perspective.isActive()); + } + Assert.assertEquals(perspectiveLabel, bot.activePerspective() + .getLabel()); + } + + /** + * @return + */ + public static Shell getMainShell() { + Shell mainWidget = UIThreadRunnable.syncExec(new Result() { + public Shell run() { + return PlatformUI.getWorkbench().getActiveWorkbenchWindow() + .getShell(); + } + }); + return mainWidget; + } + + /** + * Create an SWTBot {@link ICondition} that tests whether a given resource + * exists. + * + * @param pathToResource + * Path starting from workspace root. For example + * "kris/src/Foo.java" would refer to file "Foo.java" in the + * "src" directory of project "kris". Path's ending with trailing + * "/" will be considered to point to "IFolder" path without + * trailing "/" will be treated as "IFile" references. Paths with + * no "/" at all will be treated as references to a project. + * @return + */ + public static ResourceExists resourceExists(String pathToResource) { + return new ResourceExists(pathToResource); + } + + /** + * Wait for resource to be created. + * + * @param pathToResource + * Path starting from workspace root. For example + * "kris/src/Foo.java" would refer to file "Foo.java" in the + * "src" directory of project "kris". Path's ending with trailing + * "/" will be considered to point to "IFolder" path without + * trailing "/" will be treated as "IFile" references. Paths with + * on "/" at all will be treated as references to a project. + */ + public static void waitResourceCreated(SWTBot bot, String pathToResource, + long timeOut) { + bot.waitUntil(resourceExists(pathToResource), timeOut); + } + + /** + * Wait for resource to be created. + * + * @param pathToResource + * Path starting from workspace root. For example + * "kris/src/Foo.java" would refer to file "Foo.java" in the + * "src" directory of project "kris". Path's ending with trailing + * "/" will be considered to point to "IFolder" path without + * trailing "/" will be treated as "IFile" references. Paths with + * on "/" at all will be treated as references to a project. + */ + public static void waitResourceCreated(SWTBot bot, String pathToResource) { + waitResourceCreated(bot, pathToResource, SWTBotPreferences.TIMEOUT); + } + + /** + * @return a List of {@link Shell} under a parent. + * @param parent + * the parent under which a shell will be found. + */ + public static List shells(SWTBotShell parent) { + Matcher anyShell = widgetOfType(Shell.class); + WaitForObjectCondition waitForShell = waitForShell(anyShell, + parent.widget); + parent.bot().waitUntilWidgetAppears(waitForShell); + List allShells = waitForShell.getAllMatches(); + + System.err.println(">>> SWTBot Shells found:"); + for (Shell shell : allShells) { + SWTBotShell bot = new SWTBotShell(shell); + System.err.println(" '" + bot.getText() + "'"); + } + System.err.println("<<< SWTBot Shells found:"); + + return allShells; + } + + /** + * @return the first shell that is present or appears under a given parent. + * @param parent + * the parent under which the shell will be found. + */ + public static SWTBotShell shell(SWTBotShell parentShell) { + return new SWTBotShell(shells(parentShell).get(0)); + } + + /** + * Wait for a shell with the given regular expression. + * + * @param bot the SWTBot + * @param regex the regular expression + */ + public static void waitForShellWithRegex(SWTBot bot, String regex) { + Matcher withRegex = withRegex(regex); + WaitForObjectCondition waitForShell = waitForShell(withRegex); + bot.waitUntilWidgetAppears(waitForShell); + } + + public static void doubleClick(SWTWorkbenchBot bot, + final SWTBotTable table, int row, int col) { + table.click(row, col); + // Note: table.doubleClick() method doesn't work, table appears not to + // be listening to the events that are being posted by this method. + // Use this workaround instead: + // https://dev.eclipse.org/mhonarc/newsLists/news.eclipse.swtbot/msg00309.html + UIThreadRunnable.asyncExec(bot.getDisplay(), new VoidResult() { + public void run() { + table.widget.notifyListeners(SWT.DefaultSelection, new Event()); + } + }); + } + + /** + * A better version of the "menu" method from SWTBot. This one will retry to + * get the active shell on each poll, rather than get the active shell at + * the start and then get stuck using the wrong shell. + */ + public static SWTBotMenu menu(SWTWorkbenchBot bot, String name) { + return new SWTBotMenu(SWTBotConditions.waitForShellMenuList(bot, name, + true).get(0)); + } + + /** + * Given a view name containing a tree, this method will attempt to select a + * node contained within a parent. + * + * @param bot + * containing shell with a tree + * @param shellName + * of the shell containing tree + * @param parentName + * parent containing the node to be selected + * @param childName + * node to be selected + * @return the selected node + */ + public static SWTBotTreeItem selectChildTreeElement(SWTBot bot, + String shellName, String parentName, String childName) { + SWTBotShell shell = bot.shell(shellName); + shell.activate(); + SWTBotTreeItem parentNode = SWTBotConditions.waitForNodeExpanded(bot, + parentName); + SWTBotTreeItem childNode = SWTBotConditions.waitChildNodeToAppear(bot, + parentNode, childName); + return childNode.select(); + } + + /** + * Gets a view by name. It assumes the view MUST be opened, although not + * necessarily active. Open the view first if necessary. + * + * @return Active, focused explorer view + */ + public static SWTBotView getView(SWTWorkbenchBot bot, String name) { + SWTBotView explorer = bot.viewByTitle(name); + explorer.setFocus(); + return explorer; + } + + /** + * Selects a project in the currently opened view. A view must be opened + * prior to using this method. + * + * @param projectName + * @return + */ + public static SWTBotTree selectProject(SWTWorkbenchBot bot, + String projectName, String viewName) { + + SWTBotView explorer = getView(bot, viewName); + SWTBotTree tree = explorer.bot().tree(); + if (projectName != null) { + tree.select(projectName); + waitForSelection(bot, projectName, tree); + return tree; + } else { + tree.select(new String[0]); + waitForSelection(bot, "", tree); + return tree; + } + } + + /** + * Sometimes widgets respond slowly to selection and key presses. Use this + * method to wait for a selection to become what you expect it to be. + * Otherwise, trying to proceed will probably fail the test. + */ + public static void waitForSelection(SWTWorkbenchBot bot, + final String expected, final SWTBotList list) { + bot.waitUntil(new DefaultCondition() { + public boolean test() throws Exception { + String selection = getSelection(); + return expected.equals(selection); + } + + private String getSelection() { + String[] selections = list.selection(); + String selection = null; + if (selections.length == 1) + selection = selections[0]; + return selection; + } + + public String getFailureMessage() { + return "Was expecting selection '" + expected + " but found '" + + getSelection() + "'"; + } + }); + } + + /** + * Wait for a selection to become what you expect it to be. This method + * assumes a SWTBotTree that only has a single column selection. + */ + public static void waitForSelection(SWTWorkbenchBot bot, + final String expected, final SWTBotTree tree) { + bot.waitUntil(new DefaultCondition() { + public boolean test() throws Exception { + String selection = getSelection(tree); + return expected.equals(selection); + } + + public String getFailureMessage() { + return "Was expecting selection \n----\n" + expected + + "\n----\nbut found \n----\n" + getSelection(tree) + + "\n-----"; + } + }); + } + + public static String getSelection(SWTBotTree tree) { + TableCollection selections = tree.selection(); + String selection = ""; + for (int r = 0; r < selections.rowCount(); r++) { + if (r > 0) + selection += "\n"; + TableRow row = selections.get(r); + for (int c = 0; c < row.columnCount(); c++) { + if (c > 0) + selection += " >> "; + selection += row.get(c); + } + } + return selection; + } + + private static int screenshotNumber = 0; + + /** + * Call this method to create extra screenshots along the way. A message + * will be printed to System.err when the screenshot is taken (look in error + * log to see which screenshot file was taken when). Scrreenshots will + * include a sequence number at the start of their name that makes it easy + * to see in what order they were taken. + * + * @filename The screenshot will be save as "screenshots/filename_" + */ + public static void screenshot(String fileName) { + fileName = "screenshots/" + screenshotNumber() + fileName + "." + + SWTBotPreferences.SCREENSHOT_FORMAT.toLowerCase(); + System.err.println("Creating extra screenshot: " + fileName); + new File("screenshots").mkdirs(); //$NON-NLS-1$ + SWTUtils.captureScreenshot(fileName); + } + + private static String screenshotNumber() { + return String.format("%03d_", screenshotNumber++); + } + + public static void waitForAllBuildJobs(SWTBot bot){ + waitForAllProcessesToTerminate(bot); + waitForJobCompletions(); + bot.sleep(WAIT_FOR_BUILD_TIMEOUT); + waitForJobCompletions(); + } + + private static void waitForJobCompletions() { + StsTestUtil.waitForAutoBuild(); + StsTestUtil.waitForManualBuild(); + StsTestUtil.waitForJobFamily(ResourcesPlugin.FAMILY_AUTO_REFRESH); + StsTestUtil.waitForJobFamily(ResourcesPlugin.FAMILY_MANUAL_REFRESH); + } + + public static void waitForAllProcessesToTerminate(SWTBot bot) { + waitForAllProcessesToTerminate(bot, PROCESS_COMPLETION_TIMEOUT); + } + + public static void waitForAllProcessesToTerminate(SWTBot bot, long timeout) { + bot.waitUntil(new ICondition() { + + private IProcess activeProcess = null; + + public boolean test() throws Exception { + IProcess[] processes = DebugPlugin.getDefault().getLaunchManager().getProcesses(); + for (IProcess process : processes) { + if (!process.isTerminated()) { + print("Waiting for processes..."); + print("active = "+process.getClass()+ " " + process.getLabel()); + activeProcess = process; + return false; + } + } + print("Waiting for processes... DONE"); + return true; + } + + public void init(SWTBot bot) { + + } + + public String getFailureMessage() { + return "A process is still active:" + + "\n label = " + activeProcess.getLabel() + + "\n type = " + activeProcess.getAttribute(IProcess.ATTR_PROCESS_TYPE) + + "\n cmd = " + activeProcess.getAttribute(IProcess.ATTR_CMDLINE); + } + + }, timeout); + } + + /** + * This is only for debugging purposes, in case having trouble finding the + * menus... + */ + public static List findAllContextMenus(Shell shell) { + MenuFinder menuFinder = new ContextMenuFinder(shell); + return menuFinder.findMenus(shell, anyMenuMatcher, true); + } + + private static final Matcher anyMenuMatcher = new BaseMatcher() { + + public boolean matches(Object item) { + boolean result = item instanceof MenuItem; + if (result) { + MenuItem menu = (MenuItem) item; + System.out.println("Found menu with text : '" + menu.getText() + + "'"); + } + return result; + } + + public void describeTo(Description description) { + description.appendText("Any menu"); + } + }; + + /** + * @param explorerTree + * @return + */ + public static ICondition widgetMakeActive( + final AbstractSWTBot widget) { + return new ICondition() { + public boolean test() throws Exception { + widget.setFocus(); + return widget.isActive(); + } + + public void init(SWTBot bot) { + } + + public String getFailureMessage() { + return "Widget not active: " + widget; + } + }; + } + + private static boolean enablePrinting = true; + + public static void enablePrinting() { + enablePrinting = true; + } + + public static void disablePrinting() { + enablePrinting = false; + } + + public static void print(String message) { + if (enablePrinting) { + System.out.println(message); + } + } + + public static ICondition widgetIsDisposed( + AbstractSWTBot widget) { + return new WidgetIsDisposed(widget); + } + + public static String getConsoleText(SWTWorkbenchBot bot) { + SWTBotView console = bot.viewByTitle("Console"); + SWTBotStyledText textWidget = console.bot().styledText(); + return textWidget.getText(); + } + + /** + * Repeatedly try to find a submenu that contains a given String, + * until the menu appears, or until timeout happens. + */ + public static SWTBotMenu subMenuContaining(SWTBot bot, SWTBotMenu parentMenu, String text) { + Matcher matcher = SWTBotConditions.withTextContaining(text); + SubMenusMatching condition = new SubMenusMatching(bot, parentMenu, matcher); + return new SWTBotMenu(condition.getMenus().get(0)); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/SubMenusMatching.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/SubMenusMatching.java new file mode 100644 index 000000000..27c9b7a0c --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/SubMenusMatching.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +import static org.eclipse.swtbot.swt.finder.matchers.WidgetMatcherFactory.widgetOfType; +import static org.hamcrest.Matchers.allOf; + +import java.util.List; + +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; +import org.eclipse.swtbot.swt.finder.SWTBot; +import org.eclipse.swtbot.swt.finder.finders.MenuFinder; +import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; +import org.eclipse.swtbot.swt.finder.results.Result; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotMenu; +import org.hamcrest.Matcher; + +/** + * Find submenus of a given parent menu, matching a given condition. If none are found, + * wait/retry until timeout happens (to handle cases where menus are dynamically generated + * and may only appear after some time. + * @author Kris De Volder + */ +public class SubMenusMatching extends AbstractedWaitCondition { + private Menu parentMenu; + private boolean recursive; + private List found; + private Matcher matcher; + + public SubMenusMatching(SWTBot bot, final SWTBotMenu parentMenuBot, + Matcher matcher) { + super(bot); + this.parentMenu = UIThreadRunnable.syncExec(new Result

    () { + public Menu run() { + return parentMenuBot.widget.getMenu(); + } + }); + this.matcher = matcher; + } + + protected MenuFinder getMenuFinder() { + return new MenuFinder(); + } + + public boolean test() throws Exception { + MenuFinder finder = getMenuFinder(); + SWTBotUtils.print("Getting submenus for "+ parentMenu); + + @SuppressWarnings("unchecked") + Matcher menuMatcher = allOf(widgetOfType(MenuItem.class), matcher); + found = finder.findMenus(parentMenu, menuMatcher, recursive); + + boolean hasFound = found != null && found.size() > 0; + SWTBotUtils.print("Has found menus: " + hasFound + " for: " + matcher); + return hasFound; + } + + public List getMenus() { + waitForTest(); + return found; + } + + public void init(SWTBot bot) { + this.bot = bot; + } + + public String getFailureMessage() { + return "Failed to find menus for " + matcher + " under " +parentMenu; + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/TestKeyboard.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/TestKeyboard.java new file mode 100644 index 000000000..41fade2e3 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/TestKeyboard.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +import junit.framework.Assert; + +import org.eclipse.jface.bindings.keys.KeyStroke; +import org.eclipse.swt.SWT; +import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; +import org.eclipse.swtbot.swt.finder.keyboard.Keyboard; +import org.eclipse.swtbot.swt.finder.keyboard.KeyboardFactory; +import org.eclipse.swtbot.swt.finder.keyboard.Keystrokes; +import org.springsource.ide.eclipse.commons.tests.util.StsTestUtil; + + +/** + * Dealing with the keyboard seems to be one of the most hairy parts of SWTBot. So I'm going to + * channel all keyboard interaction through this class. With the exception of typing simple + * text (no special characters) which seems pretty safe. + *

    + * For any other type of keystroke we will add method here that can be tweaked and then re-used. + * Should not directly use keyboard or send keystrokes to any widgets because it is just too + * impredictable what will work in on different machines (even if they are both Unix!) + * @author Kris De Volder + * @author Nieraj Singh + * @created 2010-06-26 + */ +public class TestKeyboard { + + private static final KeyStroke[] ENTER_KEY = { Keystrokes.CR, Keystrokes.LF }; + //Notes: + // - with SWTKeyboard must send CR+LF for some components? + // - With AWT keyboard just sending LF seems to work ok. + // - on build server with SWTkeyboard, send CR causes assertion failed exception in SWTBot "can't post key" + + private static final KeyStroke ESCAPE_KEY = Keystrokes.ESC; + private static final KeyStroke DOWN_KEY = Keystrokes.DOWN; + private static final KeyStroke UP_KEY = Keystrokes.UP; + + private Keyboard keyboard = KeyboardFactory.getAWTKeyboard(); + private SWTWorkbenchBot bot; + + public TestKeyboard(SWTWorkbenchBot bot) { + this.bot = bot; + } + + public void CTRL_SPACE() { + keyboard.pressShortcut(SWT.CTRL, ' '); + } + public void DOWN_KEY() { + keyboard.pressShortcut(DOWN_KEY); + } + public void ENTER_KEY() { + Exception reportException = null; + boolean ok = false; + for (KeyStroke key : ENTER_KEY) { + try { + keyboard.pressShortcut(key); + ok = true; + } + catch (Exception e) { + reportException = e; + // On build machine it seems to not like one of the keys and throws an Exception, + // On my machine, some widgets seems to work ok getting one key only (LF?), but + // some other widgets only respond when sent both keys in sequence. + } + } + if (ok) //At least one key was accepted keep fingers crossed and hope this counts as "enter" + return; + else // Definitely not good, neither key was accepted. + throw new Error(reportException); + } + + public void ESCAPE_KEY() { + keyboard.pressShortcut(ESCAPE_KEY); + } + + public void typeText(String text) { + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + Assert.assertTrue("Only visible ascii no funny chars: (char)"+(int)c+" not allowed!", c>=' ' && c <= '~'); + } + keyboard.typeText(text); + } + + public void UP_KEY() { + keyboard.pressShortcut(UP_KEY); + } + + public void ALT_SHIFT_CTRL_G() { + if (StsTestUtil.isOnBuildSite()) { + // Workaround, shortcut not working on build server (no matter what I try) + // Warning: This workaround only works if Grails perspective is open! + // Only the grails perpective has the menu being used here! + bot.menu("Navigate").menu("Open Grails Command Prompt").click(); + } + else + keyboard.pressShortcut(SWT.ALT+SWT.CTRL+SWT.SHIFT, 'g'); + } + + public void ALT_G_M() throws Exception { + keyboard.pressShortcut(SWT.ALT, 'g'); + keyboard.pressShortcut(KeyStroke.getInstance("M")); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/Timewatch.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/Timewatch.java new file mode 100644 index 000000000..75db8bd04 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/Timewatch.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +import java.time.Duration; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.springsource.ide.eclipse.commons.tests.util.StsTestUtil; + +public class Timewatch { + + /** + * Runs a chunk of code on the current thread, giving a limited amount of time. + * After the time limit is exceed a thread-dump is created on sysout. + */ + public static void monitor(String taskName, Duration limit, Asserter doit) throws Exception { + AtomicBoolean done = new AtomicBoolean(false); + Job threadDumper = new Job("ThreadDumper") { + @Override + protected IStatus run(IProgressMonitor monitor) { + if (!done.get()) { + System.err.println("WARNING! "+taskName+ " is longer than "+(limit.toMillis() / 1000)+" seconds"); + System.err.println(StsTestUtil.getStackDumps()); + } + return Status.OK_STATUS; + } + }; + threadDumper.schedule(limit.toMillis()); + try { + doit.execute(); + } finally { + done.set(true); + threadDumper.cancel(); + } + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/UITestCase.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/UITestCase.java new file mode 100644 index 000000000..1b807d1b7 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/UITestCase.java @@ -0,0 +1,381 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +import java.io.File; +import java.util.List; + +import junit.framework.TestSuite; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; +import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotEditor; +import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotView; +import org.eclipse.swtbot.swt.finder.SWTBotTestCase; +import org.eclipse.swtbot.swt.finder.exceptions.WidgetNotFoundException; +import org.eclipse.swtbot.swt.finder.finders.UIThreadRunnable; +import org.eclipse.swtbot.swt.finder.results.VoidResult; +import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotList; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTree; +import org.eclipse.swtbot.swt.finder.widgets.SWTBotTreeItem; +import org.springsource.ide.eclipse.commons.tests.util.StsTestUtil; + + +/** + * @author Kris De Volder + * @author Nieraj Singh + */ +public class UITestCase extends SWTBotTestCase { + + private static Class isClassSetup = null; + private static Class setupClassTried = null; + protected static TestKeyboard keyboard; + protected static SWTWorkbenchBot bot; + protected static SWTBotShell mainShell; + private static long defaultTimeOut; + private static String defaultKeyboard; + + public static TestSuite createSuite(Class klass) { + TestSuite suite = new TestSuite(klass); + suite.addTest(TestSuite.createTest(klass, "tearDownClass")); + return suite; + } + + /** + * This method is made final, override setUpInstance and/or setupClass + * instead. + */ + @Override + public final void setUp() { + try { + if (enablePrinting()) { + SWTBotUtils.enablePrinting();; + } + if (!isClassSetup()) { + try { + setupClass(); + } finally { + setupClassTried = this.getClass(); + } + isClassSetup = this.getClass(); + } + setupInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + protected boolean enablePrinting() { + return true; + } + + protected void print(String message) { + SWTBotUtils.print(message); + } + + /** + * @return + */ + private boolean isClassSetup() { + return this.getClass() == isClassSetup; + } + + private boolean setupClassTried() { + return this.getClass() == setupClassTried; + } + + public void setupClass() throws Exception { + bot = new SWTWorkbenchBot(); + mainShell = new SWTBotShell(SWTBotUtils.getMainShell()); + System.out.println("mainShell = " + mainShell); + System.out.println(" with text= " + mainShell.getText()); + + defaultTimeOut = SWTBotPreferences.TIMEOUT; + // SWTBotPreferences.TIMEOUT = 30000; // a bit excessive? But some + // grails commands take long time. + SWTBotPreferences.KEYBOARD_LAYOUT = "EN_US"; // In superclass + keyboard = new TestKeyboard(bot); + workspaceCleanUp(); // Get rid of artefacts left behind by other badly + // behaved suites. + workspaceFileSystemCleanup(); // Cleanup artefacts left behind + // in the file system (out of synhc with Eclipse IResources) + try { + bot.viewByTitle("Welcome").close(); + } catch (WidgetNotFoundException e) { + } + + if (!setupClassTried() && !StsTestUtil.isOnBuildSite()) { + System.out.println("Deleting old screenshots..."); + File screenshotDir = new File("screenshots"); + File[] screenshots = screenshotDir.listFiles(); + if (screenshots != null) { + for (int i = 0; i < screenshots.length; i++) { + if (screenshots[i].toString().endsWith(".jpeg")) { + screenshots[i].delete(); + } + } + } + } + } + + /** + * Create a general project in the workspace. To be used as test fixture. + * + * @return an IProject referring to the create project. + * @throws CoreException + */ + protected IProject createGeneralProject(String name) throws CoreException { + IProject project = ResourcesPlugin.getWorkspace().getRoot() + .getProject(name); + if (!project.exists()) + project.create(null); + if (!project.isOpen()) + project.open(null); + return project; + } + + /** + * General cleanup: close all editors. Delete all projects. Copied from: + * StsUiTestCase + * + * @author Steffen Pingel + */ + public static void workspaceCleanUp() throws CoreException { + List editors = bot.editors(); + for (SWTBotEditor editor : editors) { + editor.close(); + } + deleteAllProjects(); + } + + public static void deleteAllProjects() throws CoreException { + IProject[] allProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); + for (IProject project : allProjects) { + project.refreshLocal(IResource.DEPTH_INFINITE, null); + project.delete(true, true, null); + } + } + + private void workspaceFileSystemCleanup() { + File workspace = StsTestUtil.getWorkspaceRoot().getLocation().toFile(); + File[] wsFiles = workspace.listFiles(); + System.out.println(">>>> wsFiles"); + for (File file : wsFiles) { + System.out.println(file); + if (file.toString().endsWith(".metadata")) { + //Skip + } + else { + deleteFiles(file); + } + } + System.out.println("<<<< wsFiles"); + } + + private void deleteFiles(File file) { + System.out.println("Deleting file: "+file); + if (file.isDirectory()) { + File[] subfiles = file.listFiles(); + for (File subfile : subfiles) { + deleteFiles(subfile); + } + } + file.delete(); + } + + private static void closeAllShells() { + Shell mainWidget = SWTBotUtils.getMainShell(); + SWTBotShell[] shells = bot.shells(); + SWTBotShell mainShell = null; + for (SWTBotShell shell : shells) { + if (shell.widget != mainWidget) { + shell.close(); + } else { + mainShell = shell; + } + } + if (mainShell != null) { + mainShell.activate(); + } + } + + + + public void setupInstance() throws Exception { + } + + @Override + protected void tearDown() throws Exception { + closeAllShells(); // crashed tests may leave open some windows / dialogs + // interfering with the + // other tests and causing cascading failures. + super.tearDown(); + } + + /** + * Warning!!! Junit3 has no mechanism to ensure that this method is called. + * You can use the createSuite method provided in this class to make a suite + * that calls this method at the end of the suite. + *

    + * For example, add the following code to your test Suite + * + * + * class MySuite { + * public static TestSuite suite() { + * TestSuite suite = ... + * ... + * suiteAddTest(GrailsUITests.createSuite(MyTests.class)); + * ... + * } + * } + * + * + * @throws Exception + */ + public void tearDownClass() throws Exception { + workspaceCleanUp(); + SWTBotPreferences.KEYBOARD_LAYOUT = defaultKeyboard; + SWTBotPreferences.TIMEOUT = defaultTimeOut; + } + + protected String getProjectViewCategory() { + return "General"; + } + + protected String getProjectViewName() { + return "Project Explorer"; + } + + /** + * Opens a particular view, from the Eclipse Window >> Show View menu. + */ + protected SWTBotView openView() { + + String category = getProjectViewCategory(); + String viewName = getProjectViewName(); + + SWTBotView explorer = null; + try { + explorer = bot.viewByTitle(viewName); + explorer.setFocus(); + } + catch (Exception e) { + e.printStackTrace(); + SWTBotUtils.menu(bot, "Window").menu("Show View").menu("Other...") + .click(); + + SWTBotUtils + .selectChildTreeElement(bot, "Show View", category, viewName); + + bot.button("OK").click(); + explorer = getView(); + } + assertTrue(explorer.isActive()); + return explorer; + } + + /** + * Gets a view by name. It assumes the view MUST be opened, although not + * necessarily active. Open the view first if necessary. + * + * @return Active, focused explorer view + */ + protected SWTBotView getView() { + return SWTBotUtils.getView(bot, getProjectViewName()); + } + + /** + * Gets a view by name. It assumes the view MUST be opened, although not necessarily active. + * Open the view first if necessary. + * @return Active, focused explorer view + */ + protected SWTBotView getView(String name) { + return SWTBotUtils.getView(bot, name); + } + + + /** + * Sometimes widgets respond slowly to selection and key presses. Use this + * method to wait for a selection to become what you expect it to be. + * Otherwise, trying to proceed will probably fail the test. + */ + protected void waitForSelection(final String expected, final SWTBotList list) { + SWTBotUtils.waitForSelection(bot, expected, list); + } + + /** + * Wait for a selection to become what you expect it to be. This method + * assumes a SWTBotTree that only has a single column selection. + */ + protected void waitForSelection(final String expected, final SWTBotTree tree) { + SWTBotUtils.waitForSelection(bot, expected, tree); + } + + protected String getSelection(SWTBotTree tree) { + return SWTBotUtils.getSelection(tree); + } + + /** + * Activate a wizard from the "File >> New" menu. + * + * @param projectWizardName + * @param wizardShellName + * @return A reference to the Wizard's shell. + */ + protected SWTBotShell activateFileNewWizardShell(String projectWizardName, + String wizardShellName) { + SWTBotUtils.menu(bot, "File").menu("New").menu(projectWizardName) + .click(); + + SWTBotShell shell = bot.shell(wizardShellName); + shell.activate(); + return shell; + } + + /** + * Selects a project in the currently opened view. A view must be opened + * prior to using this method. + * + * @param projectName + * @return + */ + protected SWTBotTree selectProject(String projectName) { + return SWTBotUtils.selectProject(bot, projectName, getProjectViewName()); + } + + protected Object getTreeItemObject(final SWTBotTreeItem item) { + final Object[] result = new Object[1]; + UIThreadRunnable.syncExec(bot.getDisplay(), new VoidResult() { + public void run() { + result[0] = item.widget.getData(); + } + }); + return result[0]; + } + + /** + * Gets the explorer tree in a currently OPENED explorer view. The explorer + * view MUST be opened before invoking this method + * + * @return explorer view tree + */ + protected SWTBotTree getExplorerViewTree() { + SWTBotView explorer = getView(); + return explorer.bot().tree(); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/WidgetIsDisposed.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/WidgetIsDisposed.java new file mode 100644 index 000000000..0126ef0e2 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/WidgetIsDisposed.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +import org.eclipse.swt.widgets.Widget; +import org.eclipse.swtbot.swt.finder.waits.DefaultCondition; +import org.eclipse.swtbot.swt.finder.widgets.AbstractSWTBot; + +/** + * @author Kris De Volder + * @author Andrew Eisenberg + */ +public class WidgetIsDisposed extends DefaultCondition { + + private AbstractSWTBot widgetToDispose; + private boolean isDisposed = false; + + public WidgetIsDisposed(AbstractSWTBot widget) { + this.widgetToDispose = widget; + } + + public boolean test() throws Exception { + bot.getDisplay().syncExec(new Runnable() { + public void run() { + isDisposed = widgetToDispose.widget.isDisposed(); + } + }); + return isDisposed; + } + public String getFailureMessage() { + return "We expected "+widgetToDispose+" to be disposed, bit it didn't happen"; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/WithTextContaining.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/WithTextContaining.java new file mode 100644 index 000000000..3fd9f94b6 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.test.util/src/org/springsource/ide/eclipse/commons/frameworks/test/util/WithTextContaining.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.test.util; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.Widget; +import org.eclipse.swtbot.swt.finder.matchers.AbstractMatcher; +import org.eclipse.swtbot.swt.finder.utils.SWTUtils; +import org.hamcrest.Description; + +/** + * Matches widgets if the getText() method of the widget contains the specified text + * as a substring. + * @author Kris De Volder + */ +public class WithTextContaining extends AbstractMatcher { + + protected String text; + + public WithTextContaining(String text) { + this.text = text; + } + + protected boolean doMatch(Object obj) { + try { + String foundText = getText(obj); + SWTBotUtils.print("Found text = "+foundText); + return foundText.contains(text); + } catch (Exception e) { + // do nothing + } + return false; + } + + static String getText(Object obj) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + return ((String) SWTUtils.invokeMethod(obj, "getText")).replaceAll(Text.DELIMITER, "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public void describeTo(Description description) { + description.appendText("with text containig'").appendText(text).appendText("'"); //$NON-NLS-1$ //$NON-NLS-2$ + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/.classpath b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/.classpath new file mode 100644 index 000000000..098194ca4 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/.project b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/.project new file mode 100644 index 000000000..94b719709 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/.project @@ -0,0 +1,28 @@ + + + org.springsource.ide.eclipse.commons.frameworks.ui + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/.settings/org.eclipse.jdt.core.prefs b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..7341ab168 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/META-INF/MANIFEST.MF b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/META-INF/MANIFEST.MF new file mode 100644 index 000000000..c77fcf493 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/META-INF/MANIFEST.MF @@ -0,0 +1,32 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %pluginName +Bundle-SymbolicName: org.springsource.ide.eclipse.commons.frameworks.ui;singleton:=true +Bundle-Version: 4.8.1.qualifier +Bundle-Activator: org.springsource.ide.eclipse.commons.frameworks.ui.FrameworkUIActivator +Bundle-Vendor: %providerName +Require-Bundle: org.eclipse.ui, + org.eclipse.jdt.core, + org.eclipse.jface.text, + org.eclipse.debug.ui, + org.eclipse.jdt.ui, + org.eclipse.ui.ide, + org.springsource.ide.eclipse.commons.frameworks.core, + org.springsource.ide.eclipse.commons.ui +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Eclipse-LazyStart: true +Bundle-ActivationPolicy: lazy +Export-Package: org.springsource.ide.eclipse.commons.frameworks.ui, + org.springsource.ide.eclipse.commons.frameworks.ui.internal.actions, + org.springsource.ide.eclipse.commons.frameworks.ui.internal.contentassist, + org.springsource.ide.eclipse.commons.frameworks.ui.internal.icons, + org.springsource.ide.eclipse.commons.frameworks.ui.internal.parameters.editors, + org.springsource.ide.eclipse.commons.frameworks.ui.internal.plugins, + org.springsource.ide.eclipse.commons.frameworks.ui.internal.swt, + org.springsource.ide.eclipse.commons.frameworks.ui.internal.tasks, + org.springsource.ide.eclipse.commons.frameworks.ui.internal.utils, + org.springsource.ide.eclipse.commons.frameworks.ui.internal.wizard +Bundle-Localization: plugin +Import-Package: org.eclipse.core.runtime, + org.eclipse.core.runtime.jobs, + org.osgi.framework diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/about.html b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/about.html new file mode 100644 index 000000000..ae88621cc --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/about.html @@ -0,0 +1,22 @@ + + + + +About Eclipse Integration Commons + + + +

    About Eclipse Integration Commons

    +

    June 30, 2015

    +

    Abstract

    +

    The Eclipse Integration Commons project provides components that help +other projects to implement Eclipse-based tooling.

    +

    License

    +

    Pivotal Software, Inc. (“Pivotal”) makes available all content +in this download ("Content"). Unless otherwise indicated below, the +Content is provided to you under the terms and conditions of the Eclipse +Public License Version 1.0 ("EPL"). A copy of the EPL is available in +the file called license.txt. For purposes of the EPL, "Program" will +mean the Content.

    + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/build.properties b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/build.properties new file mode 100644 index 000000000..848bcf1b0 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/build.properties @@ -0,0 +1,8 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + about.html,\ + plugin.properties,\ + icons/,\ + plugin.xml diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/incompatible.gif b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/incompatible.gif new file mode 100644 index 000000000..f99bdc358 Binary files /dev/null and b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/incompatible.gif differ diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/installed.gif b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/installed.gif new file mode 100644 index 000000000..7c0e89290 Binary files /dev/null and b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/installed.gif differ diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/select_install.gif b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/select_install.gif new file mode 100644 index 000000000..848eb9a0e Binary files /dev/null and b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/select_install.gif differ diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/select_uninstall.gif b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/select_uninstall.gif new file mode 100644 index 000000000..857a53798 Binary files /dev/null and b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/select_uninstall.gif differ diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/update_available.gif b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/update_available.gif new file mode 100644 index 000000000..1b5e522ee Binary files /dev/null and b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/icons/full/obj16/update_available.gif differ diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/plugin.properties b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/plugin.properties new file mode 100644 index 000000000..e064175c0 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/plugin.properties @@ -0,0 +1,2 @@ +pluginName=SpringSource Tool Suite Framework Support (UI) +providerName=Pivotal Software, Inc. diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/plugin.xml b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/plugin.xml new file mode 100644 index 000000000..0458e9bca --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/plugin.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/pom.xml b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/pom.xml new file mode 100644 index 000000000..97ec44c76 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + + org.springframework.boot.ide + org.springframework.boot.ide.servers + 4.8.1-SNAPSHOT + ../pom.xml + + + org.springsource.ide.eclipse.commons.frameworks.ui + eclipse-plugin + org.springsource.ide.eclipse.commons.frameworks.ui + + + + + org.eclipse.tycho + tycho-p2-plugin + ${tycho-version} + + + second-generate-p2-metadata + + p2-metadata + + verify + + + + + + + diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/FrameworkUIActivator.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/FrameworkUIActivator.java new file mode 100644 index 000000000..ff6ede0f4 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/FrameworkUIActivator.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.ui.plugin.AbstractUIPlugin; +import org.osgi.framework.BundleContext; +import org.springsource.ide.eclipse.commons.frameworks.ui.internal.legacyconversion.LegacyProjectListener; + +/** + * @author Nieraj Singh + * @author Christian Dupuis + * @since 2.5.0 + */ +public class FrameworkUIActivator extends AbstractUIPlugin { + + // The plug-in ID + public static final String PLUGIN_ID = "org.springsource.ide.eclipse.commons.frameworks.ui"; //$NON-NLS-1$ + + // The shared instance + private static FrameworkUIActivator plugin; + + /** + * The constructor + */ + public FrameworkUIActivator() { + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + } + + /* + * (non-Javadoc) + * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext context) throws Exception { + plugin = null; + LegacyProjectListener.LISTENER.dispose(); + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static FrameworkUIActivator getDefault() { + return plugin; + } + + public static ImageDescriptor getImageDescriptor(String path) { + return imageDescriptorFromPlugin(PLUGIN_ID, path); + } + + public static void log(IStatus status) { + getDefault().getLog().log(status); + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/actions/AbstractActionDelegate.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/actions/AbstractActionDelegate.java new file mode 100644 index 000000000..2d603222c --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/actions/AbstractActionDelegate.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.actions; + +import java.util.List; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IObjectActionDelegate; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchSite; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.IWorkbenchWindowActionDelegate; +import org.springsource.ide.eclipse.commons.frameworks.ui.internal.utils.ProjectFilter; +import org.springsource.ide.eclipse.commons.frameworks.ui.internal.utils.SelectionUtils; + + +/** + * @author Nieraj Singh + * @author Kris De Volder + */ +public abstract class AbstractActionDelegate implements IObjectActionDelegate, + IWorkbenchWindowActionDelegate { + + private static final String DEBUG_CLASS = null; + //"org.grails.ide.eclipse.ui.internal.actions.OpenGrailsPluginsManagerActionDelegate"; + + protected void debug(String msg) { + if (this.getClass().getName().equals(DEBUG_CLASS)) { + System.err.println(msg); + } + } + + public void dispose() { + } + + public void init(IWorkbenchWindow window) { + } + + protected List selectedProjects; + protected IWorkbenchPart part; + + public void setActivePart(IAction action, IWorkbenchPart part) { + this.part = part; + } + + /** + * Gets the workbench. It may be null if it cannot be resolved. + * + * @return workbench or null if it cannot be resolved + */ + protected IWorkbench getWorkbench() { + if (part != null) { + IWorkbenchSite site = part.getSite(); + if (site != null) { + return site.getWorkbenchWindow().getWorkbench(); + } + } + return null; + } + + public void selectionChanged(IAction action, ISelection sel) { + if (sel==null) return; //ignore spurrious null selections + + List newSelectedProjects = SelectionUtils.getProjects(sel, new ProjectFilter() { + @Override + public boolean isAcceptable(IProject project) { + return shouldAddToProjectList(project); + } + }); + + //Try to hold on to valid selection as long as possible, so only replace selectedProjects if + // we have some actual new selections. + if (!newSelectedProjects.isEmpty()) { + debug(this + ":" + newSelectedProjects.toString()); + selectedProjects = newSelectedProjects; + } + if (selectedProjects==null || selectedProjects.isEmpty()) { + action.setEnabled(false); + } else { + action.setEnabled(true); + } + + } + + protected boolean shouldAddToProjectList(IProject project) { + return project != null && project.exists() && project.isAccessible(); + } + + protected List getSelectedProjects() { + return selectedProjects; + } + + protected Shell getShell() { + Shell shell = null; + if (part != null) { + shell = part.getSite().getShell(); + } + + if (shell == null) { + shell = Display.getDefault().getActiveShell(); + } + + return shell; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/actions/AbstractCommandActionDelegate.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/actions/AbstractCommandActionDelegate.java new file mode 100644 index 000000000..a3a5a9ec8 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/actions/AbstractCommandActionDelegate.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.actions; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.layout.PixelConverter; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.wizard.WizardDialog; +import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.IFrameworkCommand; +import org.springsource.ide.eclipse.commons.frameworks.ui.internal.wizard.GenericCommandWizard; + + +/** + * @author Nieraj Singh + * @author Kris De Volder + */ +public abstract class AbstractCommandActionDelegate extends + AbstractActionDelegate { + + private IFrameworkCommand selectedCommand = null; + + public void selectionChanged(IAction action, ISelection sel) { + debug("Selection Changed: "+sel); + if (sel!=null) { + //It seems we receive "null" selection when the Key shortcut popup disapears (Linux). This happens despite the fact + //that the selection in the workbench has not been changed. This causes problems (command gets deactivated or + //work on wrong selection, so we ignore any "null" selections. + super.selectionChanged(action, sel); + selectedCommand = getSelectedCommand(action, sel); + } + } + + public void run(IAction action) { + List projects = getSelectedProjects(); + IProject project = null; + if (projects!=null) { + project = getSelectedProjects().get(0); + } + if (project != null) { + LinkedHashSet allProjects = new LinkedHashSet(); + + // Add the selected Project first + allProjects.add(project); + + // Add any remaining projects that should appear as selectable + // projects in the wizard + Collection selectionProjects = getSelectionProjects(project); + if (selectionProjects != null) { + allProjects.addAll(selectionProjects); + } + + GenericCommandWizard wizard = getCommandWizard(allProjects, + selectedCommand); + + if (wizard == null) { + MessageDialog + .openError( + getShell(), + "Unable to find command wizard", + "No command wizard found. This action requires a command wizard. Please contact STS support."); + return; + } + + WizardDialog dialog = new WizardDialog(getShell(), wizard); + + PixelConverter converter = new PixelConverter( + JFaceResources.getDialogFont()); + dialog.setPageSize( + converter.convertWidthInCharsToPixels(70), + converter.convertHeightInCharsToPixels(20)); + dialog.create(); + + if (Dialog.OK == dialog.open()) { + //The call below is superfluous, performFinish was already called automatically when user clicks finish button! + // wizard.performFinish(); + } + } + } + + /** + * Return a list of projects to select. The project argument is the + * currently selected project by the context menu action. + *

    + * Note that the selected project need NOT be added to the list, as it is + * already selected. This list should have any other project that would also + * be valid selections. + *

    + * + * @param selectedProject + * project currently selected by context menu action + * @return list of OTHER projects that are also valid selections. Can be + * null or empty. + */ + protected Collection getSelectionProjects(IProject selectedProject) { + return null; + } + + /** + * Optional. Based on the action and selection, it is possible to create a + * command INSTANCE that is prepopulated with parameter values. Note that + * these are not necessarily default values for the parameters, but + * prepopulated values specific to the action or selection. Can be null. + * + * @param action + * currently selected action + * @param selection + * selection in workbench + * @return a pre-filled command instance, or null if not required. + */ + protected IFrameworkCommand getSelectedCommand(IAction action, + ISelection selection) { + return null; + } + + /** + * Get the command wizard that allows a user to select commands and + * configure a command instance. + * + * @param projects + * @param command + * @return + */ + abstract protected GenericCommandWizard getCommandWizard(Collection projects, IFrameworkCommand command); + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/AbstractJavaContentAsssitHandler.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/AbstractJavaContentAsssitHandler.java new file mode 100644 index 000000000..f64487aa5 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/AbstractJavaContentAsssitHandler.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.contentassist; + +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + + +/** + * @author Nieraj Singh + */ +public abstract class AbstractJavaContentAsssitHandler implements IJavaContentAssistHandler { + + private Button browseButton; + private Text javaText; + + public AbstractJavaContentAsssitHandler(Text javaText, Button browseButton) { + this.browseButton = browseButton; + this.javaText = javaText; + } + + /* (non-Javadoc) + * @see com.springsource.sts.frameworks.ui.internal.parameters.editors.IJavaContentAssistHandler#getBrowseButtonControl() + */ + public Button getBrowseButtonControl() { + return browseButton; + } + + /* (non-Javadoc) + * @see com.springsource.sts.frameworks.ui.internal.parameters.editors.IJavaContentAssistHandler#getJavaTextControl() + */ + public Text getJavaTextControl() { + return javaText; + } + + /* (non-Javadoc) + * @see com.springsource.sts.frameworks.ui.internal.parameters.editors.IJavaContentAssistHandler#getShell() + */ + public Shell getShell() { + // No null check. This should not be null. + return javaText.getShell(); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/ContentProposalAdapter.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/ContentProposalAdapter.java new file mode 100644 index 000000000..887839da2 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/ContentProposalAdapter.java @@ -0,0 +1,2221 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.contentassist; + +import java.util.ArrayList; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.jface.bindings.keys.KeyStroke; +import org.eclipse.jface.dialogs.PopupDialog; +import org.eclipse.jface.fieldassist.IContentProposal; +import org.eclipse.jface.fieldassist.IContentProposalListener; +import org.eclipse.jface.fieldassist.IContentProposalProvider; +import org.eclipse.jface.fieldassist.IControlContentAdapter; +import org.eclipse.jface.fieldassist.IControlContentAdapter2; +import org.eclipse.jface.fieldassist.SimpleContentProposalProvider; +import org.eclipse.jface.preference.JFacePreferences; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.util.Util; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.ScrollBar; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Text; + +/** + * ContentProposalAdapter can be used to attach content proposal behavior to a + * control. This behavior includes obtaining proposals, opening a popup dialog, + * managing the content of the control relative to the selections in the popup, + * and optionally opening up a secondary popup to further describe proposals. + *

    + * A number of configurable options are provided to determine how the control + * content is altered when a proposal is chosen, how the content proposal popup + * is activated, and whether any filtering should be done on the proposals as + * the user types characters. + *

    + * This class provides some overridable methods to allow clients to manually + * control the popup. However, most of the implementation remains private. + * @author Kris De Volder + * @author Nieraj Singh + * @since 3.2 + */ +public class ContentProposalAdapter { + + /* + * The lightweight popup used to show content proposals for a text field. If + * additional information exists for a proposal, then selecting that + * proposal will result in the information being displayed in a secondary + * popup. + */ + class ContentProposalPopup extends PopupDialog { + /* + * The listener we install on the popup and related controls to + * determine when to close the popup. Some events (move, resize, close, + * deactivate) trigger closure as soon as they are received, simply + * because one of the registered listeners received them. Other events + * depend on additional circumstances. + */ + private final class PopupCloserListener implements Listener { + private boolean scrollbarClicked = false; + + public void handleEvent(final Event e) { + + // If focus is leaving an important widget or the field's + // shell is deactivating + if (e.type == SWT.FocusOut) { + scrollbarClicked = false; + /* + * Ignore this event if it's only happening because focus is + * moving between the popup shells, their controls, or a + * scrollbar. Do this in an async since the focus is not + * actually switched when this event is received. + */ + e.display.asyncExec(new Runnable() { + public void run() { + if (isValid()) { + if (scrollbarClicked || hasFocus()) { + return; + } + // Workaround a problem on X and Mac, whereby at + // this point, the focus control is not known. + // This can happen, for example, when resizing + // the popup shell on the Mac. + // Check the active shell. + Shell activeShell = e.display.getActiveShell(); + if (activeShell == getShell() + || (infoPopup != null && infoPopup + .getShell() == activeShell)) { + return; + } + /* + * System.out.println(e); + * System.out.println(e.display.getFocusControl()); + * System.out.println(e.display.getActiveShell()); + */ + close(); + } + } + }); + return; + } + + // Scroll bar has been clicked. Remember this for focus event + // processing. + if (e.type == SWT.Selection) { + scrollbarClicked = true; + return; + } + // For all other events, merely getting them dictates closure. + /* STS Change: new code: */ + e.display.asyncExec(new Runnable() { + public void run() { + close(); + } + }); + /* STS Change: old code: + close(); + */ + } + + // Install the listeners for events that need to be monitored for + // popup closure. + void installListeners() { + // Listeners on this popup's table and scroll bar + proposalTable.addListener(SWT.FocusOut, this); + ScrollBar scrollbar = proposalTable.getVerticalBar(); + if (scrollbar != null) { + scrollbar.addListener(SWT.Selection, this); + } + + // Listeners on this popup's shell + getShell().addListener(SWT.Deactivate, this); + getShell().addListener(SWT.Close, this); + + // Listeners on the target control + control.addListener(SWT.MouseDoubleClick, this); + control.addListener(SWT.MouseDown, this); + control.addListener(SWT.Dispose, this); + control.addListener(SWT.FocusOut, this); + // Listeners on the target control's shell + Shell controlShell = control.getShell(); + controlShell.addListener(SWT.Move, this); + controlShell.addListener(SWT.Resize, this); + + } + + // Remove installed listeners + void removeListeners() { + if (isValid()) { + proposalTable.removeListener(SWT.FocusOut, this); + ScrollBar scrollbar = proposalTable.getVerticalBar(); + if (scrollbar != null) { + scrollbar.removeListener(SWT.Selection, this); + } + + getShell().removeListener(SWT.Deactivate, this); + getShell().removeListener(SWT.Close, this); + } + + if (control != null && !control.isDisposed()) { + + control.removeListener(SWT.MouseDoubleClick, this); + control.removeListener(SWT.MouseDown, this); + control.removeListener(SWT.Dispose, this); + control.removeListener(SWT.FocusOut, this); + + Shell controlShell = control.getShell(); + controlShell.removeListener(SWT.Move, this); + controlShell.removeListener(SWT.Resize, this); + } + } + } + + /* + * The listener we will install on the target control. + */ + private final class TargetControlListener implements Listener { + // Key events from the control + public void handleEvent(Event e) { + if (!isValid()) { + return; + } + + char key = e.character; + + // Traverse events are handled depending on whether the + // event has a character. + if (e.type == SWT.Traverse) { + // If the traverse event contains a legitimate character, + // then we must set doit false so that the widget will + // receive the key event. We return immediately so that + // the character is handled only in the key event. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=132101 + if (key != 0) { + e.doit = false; + return; + } + // Traversal does not contain a character. Set doit true + // to indicate TRAVERSE_NONE will occur and that no key + // event will be triggered. We will check for navigation + // keys below. + e.detail = SWT.TRAVERSE_NONE; + e.doit = true; + } else { + // Default is to only propagate when configured that way. + // Some keys will always set doit to false anyway. + e.doit = propagateKeys; + } + + // No character. Check for navigation keys. + + if (key == 0) { + int newSelection = proposalTable.getSelectionIndex(); + int visibleRows = (proposalTable.getSize().y / proposalTable + .getItemHeight()) - 1; + switch (e.keyCode) { + case SWT.ARROW_UP: + newSelection -= 1; + if (newSelection < 0) { + newSelection = proposalTable.getItemCount() - 1; + } + // Not typical - usually we get this as a Traverse and + // therefore it never propagates. Added for consistency. + if (e.type == SWT.KeyDown) { + // don't propagate to control + e.doit = false; + } + + break; + + case SWT.ARROW_DOWN: + newSelection += 1; + if (newSelection > proposalTable.getItemCount() - 1) { + newSelection = 0; + } + // Not typical - usually we get this as a Traverse and + // therefore it never propagates. Added for consistency. + if (e.type == SWT.KeyDown) { + // don't propagate to control + e.doit = false; + } + + break; + + case SWT.PAGE_DOWN: + newSelection += visibleRows; + if (newSelection >= proposalTable.getItemCount()) { + newSelection = proposalTable.getItemCount() - 1; + } + if (e.type == SWT.KeyDown) { + // don't propagate to control + e.doit = false; + } + break; + + case SWT.PAGE_UP: + newSelection -= visibleRows; + if (newSelection < 0) { + newSelection = 0; + } + if (e.type == SWT.KeyDown) { + // don't propagate to control + e.doit = false; + } + break; + + case SWT.HOME: + newSelection = 0; + if (e.type == SWT.KeyDown) { + // don't propagate to control + e.doit = false; + } + break; + + case SWT.END: + newSelection = proposalTable.getItemCount() - 1; + if (e.type == SWT.KeyDown) { + // don't propagate to control + e.doit = false; + } + break; + + // If received as a Traverse, these should propagate + // to the control as keydown. If received as a keydown, + // proposals should be recomputed since the cursor + // position has changed. + case SWT.ARROW_LEFT: + case SWT.ARROW_RIGHT: + if (e.type == SWT.Traverse) { + e.doit = false; + } else { + e.doit = true; + String contents = getControlContentAdapter() + .getControlContents(getControl()); + // If there are no contents, changes in cursor + // position have no effect. Note also that we do + // not affect the filter text on ARROW_LEFT as + // we would with BS. + if (contents.length() > 0) { + asyncRecomputeProposals(filterText); + } + } + break; + + // Any unknown keycodes will cause the popup to close. + // Modifier keys are explicitly checked and ignored because + // they are not complete yet (no character). + default: + if (e.keyCode != SWT.CAPS_LOCK && e.keyCode != SWT.NUM_LOCK + && e.keyCode != SWT.MOD1 + && e.keyCode != SWT.MOD2 + && e.keyCode != SWT.MOD3 + && e.keyCode != SWT.MOD4) { + close(); + } + return; + } + + // If any of these navigation events caused a new selection, + // then handle that now and return. + if (newSelection >= 0) { + selectProposal(newSelection); + } + return; + } + + // key != 0 + // Check for special keys involved in cancelling, accepting, or + // filtering the proposals. + switch (key) { + case SWT.ESC: + e.doit = false; + close(); + break; + + case SWT.LF: + case SWT.CR: + e.doit = false; + Object p = getSelectedProposal(); + if (p != null) { + acceptCurrentProposal(); + } else { + close(); + } + break; + + case SWT.TAB: + e.doit = false; + getShell().setFocus(); + return; + + case SWT.BS: + // Backspace should back out of any stored filter text + if (filterStyle != FILTER_NONE) { + // We have no filter to back out of, so do nothing + if (filterText.length() == 0) { + return; + } + // There is filter to back out of + filterText = filterText.substring(0, filterText + .length() - 1); + asyncRecomputeProposals(filterText); + return; + } + // There is no filtering provided by us, but some + // clients provide their own filtering based on content. + // Recompute the proposals if the cursor position + // will change (is not at 0). + int pos = getControlContentAdapter().getCursorPosition( + getControl()); + // We rely on the fact that the contents and pos do not yet + // reflect the result of the BS. If the contents were + // already empty, then BS should not cause + // a recompute. + if (pos > 0) { + asyncRecomputeProposals(filterText); + } + break; + + default: + // If the key is a defined unicode character, and not one of + // the special cases processed above, update the filter text + // and filter the proposals. + if (Character.isDefined(key)) { + if (filterStyle == FILTER_CUMULATIVE) { + filterText = filterText + String.valueOf(key); + } else if (filterStyle == FILTER_CHARACTER) { + filterText = String.valueOf(key); + } + // Recompute proposals after processing this event. + asyncRecomputeProposals(filterText); + } + break; + } + } + } + + /* + * Internal class used to implement the secondary popup. + */ + private class InfoPopupDialog extends PopupDialog { + + /* + * The text control that displays the text. + */ + private Text text; + + /* + * The String shown in the popup. + */ + private String contents = EMPTY; + + /* + * Construct an info-popup with the specified parent. + */ + InfoPopupDialog(Shell parent) { + super(parent, PopupDialog.HOVER_SHELLSTYLE, false, false, false, + false, false, null, null); + } + + /* + * Create a text control for showing the info about a proposal. + */ + protected Control createDialogArea(Composite parent) { + text = new Text(parent, SWT.MULTI | SWT.READ_ONLY | SWT.WRAP + | SWT.NO_FOCUS); + + // Use the compact margins employed by PopupDialog. + GridData gd = new GridData(GridData.BEGINNING + | GridData.FILL_BOTH); + gd.horizontalIndent = PopupDialog.POPUP_HORIZONTALSPACING; + gd.verticalIndent = PopupDialog.POPUP_VERTICALSPACING; + text.setLayoutData(gd); + text.setText(contents); + + // since SWT.NO_FOCUS is only a hint... + text.addFocusListener(new FocusAdapter() { + public void focusGained(FocusEvent event) { + ContentProposalPopup.this.close(); + } + }); + return text; + } + + /* + * Adjust the bounds so that we appear adjacent to our parent shell + */ + protected void adjustBounds() { + Rectangle parentBounds = getParentShell().getBounds(); + Rectangle proposedBounds; + // Try placing the info popup to the right + Rectangle rightProposedBounds = new Rectangle(parentBounds.x + + parentBounds.width + + PopupDialog.POPUP_HORIZONTALSPACING, parentBounds.y + + PopupDialog.POPUP_VERTICALSPACING, + parentBounds.width, parentBounds.height); + rightProposedBounds = getConstrainedShellBounds(rightProposedBounds); + // If it won't fit on the right, try the left + if (rightProposedBounds.intersects(parentBounds)) { + Rectangle leftProposedBounds = new Rectangle(parentBounds.x + - parentBounds.width - POPUP_HORIZONTALSPACING - 1, + parentBounds.y, parentBounds.width, + parentBounds.height); + leftProposedBounds = getConstrainedShellBounds(leftProposedBounds); + // If it won't fit on the left, choose the proposed bounds + // that fits the best + if (leftProposedBounds.intersects(parentBounds)) { + if (rightProposedBounds.x - parentBounds.x >= parentBounds.x + - leftProposedBounds.x) { + rightProposedBounds.x = parentBounds.x + + parentBounds.width + + PopupDialog.POPUP_HORIZONTALSPACING; + proposedBounds = rightProposedBounds; + } else { + leftProposedBounds.width = parentBounds.x + - POPUP_HORIZONTALSPACING + - leftProposedBounds.x; + proposedBounds = leftProposedBounds; + } + } else { + // use the proposed bounds on the left + proposedBounds = leftProposedBounds; + } + } else { + // use the proposed bounds on the right + proposedBounds = rightProposedBounds; + } + getShell().setBounds(proposedBounds); + } + + /* + * (non-Javadoc) + * @see org.eclipse.jface.dialogs.PopupDialog#getForeground() + */ + protected Color getForeground() { + return control.getDisplay(). + getSystemColor(SWT.COLOR_INFO_FOREGROUND); + } + + /* + * (non-Javadoc) + * @see org.eclipse.jface.dialogs.PopupDialog#getBackground() + */ + protected Color getBackground() { + return control.getDisplay(). + getSystemColor(SWT.COLOR_INFO_BACKGROUND); + } + + /* + * Set the text contents of the popup. + */ + void setContents(String newContents) { + if (newContents == null) { + newContents = EMPTY; + } + this.contents = newContents; + if (text != null && !text.isDisposed()) { + text.setText(contents); + } + } + + /* + * Return whether the popup has focus. + */ + boolean hasFocus() { + if (text == null || text.isDisposed()) { + return false; + } + return text.getShell().isFocusControl() + || text.isFocusControl(); + } + } + + /* + * The listener installed on the target control. + */ + private Listener targetControlListener; + + /* + * The listener installed in order to close the popup. + */ + private PopupCloserListener popupCloser; + + /* + * The table used to show the list of proposals. + */ + private Table proposalTable; + + /* + * The proposals to be shown (cached to avoid repeated requests). + */ + private IContentProposal[] proposals; + + /* + * Secondary popup used to show detailed information about the selected + * proposal.. + */ + private InfoPopupDialog infoPopup; + + /* + * Flag indicating whether there is a pending secondary popup update. + */ + private boolean pendingDescriptionUpdate = false; + + /* + * Filter text - tracked while popup is open, only if we are told to + * filter + */ + private String filterText = EMPTY; + + /** + * Constructs a new instance of this popup, specifying the control for + * which this popup is showing content, and how the proposals should be + * obtained and displayed. + * + * @param infoText + * Text to be shown in a lower info area, or + * null if there is no info area. + */ + ContentProposalPopup(String infoText, IContentProposal[] proposals) { + // IMPORTANT: Use of SWT.ON_TOP is critical here for ensuring + // that the target control retains focus on Mac and Linux. Without + // it, the focus will disappear, keystrokes will not go to the + // popup, and the popup closer will wrongly close the popup. + // On platforms where SWT.ON_TOP overrides SWT.RESIZE, we will live + // with this. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=126138 + super(control.getShell(), SWT.RESIZE | SWT.ON_TOP, false, false, false, + false, false, null, infoText); + this.proposals = proposals; + } + + /* + * (non-Javadoc) + * @see org.eclipse.jface.dialogs.PopupDialog#getForeground() + */ + protected Color getForeground() { + return JFaceResources.getColorRegistry().get( + JFacePreferences.CONTENT_ASSIST_FOREGROUND_COLOR); + } + + /* + * (non-Javadoc) + * @see org.eclipse.jface.dialogs.PopupDialog#getBackground() + */ + protected Color getBackground() { + return JFaceResources.getColorRegistry().get( + JFacePreferences.CONTENT_ASSIST_BACKGROUND_COLOR); + } + + /* + * Creates the content area for the proposal popup. This creates a table + * and places it inside the composite. The table will contain a list of + * all the proposals. + * + * @param parent The parent composite to contain the dialog area; must + * not be null. + */ + protected final Control createDialogArea(final Composite parent) { + // Use virtual where appropriate (see flag definition). + if (USE_VIRTUAL) { + proposalTable = new Table(parent, SWT.H_SCROLL | SWT.V_SCROLL + | SWT.VIRTUAL); + + Listener listener = new Listener() { + public void handleEvent(Event event) { + handleSetData(event); + } + }; + proposalTable.addListener(SWT.SetData, listener); + } else { + proposalTable = new Table(parent, SWT.H_SCROLL | SWT.V_SCROLL); + } + + // set the proposals to force population of the table. + setProposals(filterProposals(proposals, filterText)); + + proposalTable.setHeaderVisible(false); + proposalTable.addSelectionListener(new SelectionListener() { + + public void widgetSelected(SelectionEvent e) { + // If a proposal has been selected, show it in the secondary + // popup. Otherwise close the popup. + if (e.item == null) { + if (infoPopup != null) { + infoPopup.close(); + } + } else { + showProposalDescription(); + } + } + + // Default selection was made. Accept the current proposal. + public void widgetDefaultSelected(SelectionEvent e) { + acceptCurrentProposal(); + } + }); + return proposalTable; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.dialogs.PopupDialog.adjustBounds() + */ + protected void adjustBounds() { + // Get our control's location in display coordinates. + Point location = control.getDisplay().map(control.getParent(), null, control.getLocation()); + int initialX = location.x + POPUP_OFFSET; + int initialY = location.y + control.getSize().y + POPUP_OFFSET; + // If we are inserting content, use the cursor position to + // position the control. + if (getProposalAcceptanceStyle() == PROPOSAL_INSERT) { + Rectangle insertionBounds = controlContentAdapter + .getInsertionBounds(control); + initialX = initialX + insertionBounds.x; + initialY = location.y + insertionBounds.y + + insertionBounds.height; + } + + // If there is no specified size, force it by setting + // up a layout on the table. + if (popupSize == null) { + GridData data = new GridData(GridData.FILL_BOTH); + data.heightHint = proposalTable.getItemHeight() + * POPUP_CHAR_HEIGHT; + data.widthHint = Math.max(control.getSize().x, + POPUP_MINIMUM_WIDTH); + proposalTable.setLayoutData(data); + getShell().pack(); + popupSize = getShell().getSize(); + } + + // Constrain to the display + Rectangle constrainedBounds = getConstrainedShellBounds(new Rectangle(initialX, initialY, popupSize.x, popupSize.y)); + + // If there has been an adjustment causing the popup to overlap + // with the control, then put the popup above the control. + if (constrainedBounds.y < initialY) + getShell().setBounds(initialX, location.y - popupSize.y, popupSize.x, popupSize.y); + else + getShell().setBounds(initialX, initialY, popupSize.x, popupSize.y); + + // Now set up a listener to monitor any changes in size. + getShell().addListener(SWT.Resize, new Listener() { + public void handleEvent(Event e) { + popupSize = getShell().getSize(); + if (infoPopup != null) { + infoPopup.adjustBounds(); + } + } + }); + } + + /* + * Handle the set data event. Set the item data of the requested item to + * the corresponding proposal in the proposal cache. + */ + private void handleSetData(Event event) { + TableItem item = (TableItem) event.item; + int index = proposalTable.indexOf(item); + + if (0 <= index && index < proposals.length) { + IContentProposal current = proposals[index]; + item.setText(getString(current)); + item.setImage(getImage(current)); + item.setData(current); + } else { + // this should not happen, but does on win32 + } + } + + /* + * Caches the specified proposals and repopulates the table if it has + * been created. + */ + private void setProposals(IContentProposal[] newProposals) { + if (newProposals == null || newProposals.length == 0) { + newProposals = getEmptyProposalArray(); + } + this.proposals = newProposals; + + // If there is a table + if (isValid()) { + final int newSize = newProposals.length; + if (USE_VIRTUAL) { + // Set and clear the virtual table. Data will be + // provided in the SWT.SetData event handler. + proposalTable.setItemCount(newSize); + proposalTable.clearAll(); + } else { + // Populate the table manually + proposalTable.setRedraw(false); + proposalTable.setItemCount(newSize); + TableItem[] items = proposalTable.getItems(); + for (int i = 0; i < items.length; i++) { + TableItem item = items[i]; + IContentProposal proposal = newProposals[i]; + item.setText(getString(proposal)); + item.setImage(getImage(proposal)); + item.setData(proposal); + } + proposalTable.setRedraw(true); + } + // Default to the first selection if there is content. + if (newProposals.length > 0) { + selectProposal(0); + } else { + // No selection, close the secondary popup if it was open + if (infoPopup != null) { + infoPopup.close(); + } + + } + } + } + + /* + * Get the string for the specified proposal. Always return a String of + * some kind. + */ + private String getString(IContentProposal proposal) { + if (proposal == null) { + return EMPTY; + } + if (labelProvider == null) { + return proposal.getLabel() == null ? proposal.getContent() + : proposal.getLabel(); + } + return labelProvider.getText(proposal); + } + + /* + * Get the image for the specified proposal. If there is no image + * available, return null. + */ + private Image getImage(IContentProposal proposal) { + if (proposal == null || labelProvider == null) { + return null; + } + return labelProvider.getImage(proposal); + } + + /* + * Return an empty array. Used so that something always shows in the + * proposal popup, even if no proposal provider was specified. + */ + private IContentProposal[] getEmptyProposalArray() { + return new IContentProposal[0]; + } + + /* + * Answer true if the popup is valid, which means the table has been + * created and not disposed. + */ + private boolean isValid() { + return proposalTable != null && !proposalTable.isDisposed(); + } + + /* + * Return whether the receiver has focus. Since 3.4, this includes a + * check for whether the info popup has focus. + */ + private boolean hasFocus() { + if (!isValid()) { + return false; + } + if (getShell().isFocusControl() || proposalTable.isFocusControl()) { + return true; + } + if (infoPopup != null && infoPopup.hasFocus()) { + return true; + } + return false; + } + + /* + * Return the current selected proposal. + */ + private IContentProposal getSelectedProposal() { + if (isValid()) { + int i = proposalTable.getSelectionIndex(); + if (proposals == null || i < 0 || i >= proposals.length) { + return null; + } + return proposals[i]; + } + return null; + } + + /* + * Select the proposal at the given index. + */ + private void selectProposal(int index) { + Assert + .isTrue(index >= 0, + "Proposal index should never be negative"); //$NON-NLS-1$ + if (!isValid() || proposals == null || index >= proposals.length) { + return; + } + proposalTable.setSelection(index); + proposalTable.showSelection(); + + showProposalDescription(); + } + + /** + * Opens this ContentProposalPopup. This method is extended in order to + * add the control listener when the popup is opened and to invoke the + * secondary popup if applicable. + * + * @return the return code + * + * @see org.eclipse.jface.window.Window#open() + */ + public int open() { + int value = super.open(); + if (popupCloser == null) { + popupCloser = new PopupCloserListener(); + } + popupCloser.installListeners(); + IContentProposal p = getSelectedProposal(); + if (p != null) { + showProposalDescription(); + } + return value; + } + + /** + * Closes this popup. This method is extended to remove the control + * listener. + * + * @return true if the window is (or was already) closed, + * and false if it is still open + */ + public boolean close() { + popupCloser.removeListeners(); + if (infoPopup != null) { + infoPopup.close(); + } + boolean ret = super.close(); + notifyPopupClosed(); + return ret; + } + + /* + * Show the currently selected proposal's description in a secondary + * popup. + */ + private void showProposalDescription() { + // If we do not already have a pending update, then + // create a thread now that will show the proposal description + if (!pendingDescriptionUpdate) { + // Create a thread that will sleep for the specified delay + // before creating the popup. We do not use Jobs since this + // code must be able to run independently of the Eclipse + // runtime. + Runnable runnable = new Runnable() { + public void run() { + pendingDescriptionUpdate = true; + try { + Thread.sleep(POPUP_DELAY); + } catch (InterruptedException e) { + } + if (!isValid()) { + return; + } + getShell().getDisplay().syncExec(new Runnable() { + public void run() { + // Query the current selection since we have + // been delayed + IContentProposal p = getSelectedProposal(); + if (p != null) { + String description = p.getDescription(); + if (description != null) { + if (infoPopup == null) { + infoPopup = new InfoPopupDialog( + getShell()); + infoPopup.open(); + infoPopup + .getShell() + .addDisposeListener( + new DisposeListener() { + public void widgetDisposed( + DisposeEvent event) { + infoPopup = null; + } + }); + } + infoPopup.setContents(p + .getDescription()); + } else if (infoPopup != null) { + infoPopup.close(); + } + pendingDescriptionUpdate = false; + + } + } + }); + } + }; + Thread t = new Thread(runnable); + t.start(); + } + } + + /* + * Accept the current proposal. + */ + private void acceptCurrentProposal() { + // Close before accepting the proposal. This is important + // so that the cursor position can be properly restored at + // acceptance, which does not work without focus on some controls. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127108 + IContentProposal proposal = getSelectedProposal(); + close(); + proposalAccepted(proposal); + } + + /* + * Request the proposals from the proposal provider, and recompute any + * caches. Repopulate the popup if it is open. + */ + private void recomputeProposals(String filterText) { + IContentProposal[] allProposals = getProposals(); + if (allProposals == null) + allProposals = getEmptyProposalArray(); + // If the non-filtered proposal list is empty, we should + // close the popup. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=147377 + if (allProposals.length == 0) { + proposals = allProposals; + close(); + } else { + // Keep the popup open, but filter by any provided filter text + setProposals(filterProposals(allProposals, filterText)); + } + } + + /* + * In an async block, request the proposals. This is used when clients + * are in the middle of processing an event that affects the widget + * content. By using an async, we ensure that the widget content is up + * to date with the event. + */ + private void asyncRecomputeProposals(final String filterText) { + if (isValid()) { + control.getDisplay().asyncExec(new Runnable() { + public void run() { + recordCursorPosition(); + recomputeProposals(filterText); + } + }); + } else { + recomputeProposals(filterText); + } + } + + /* + * Filter the provided list of content proposals according to the filter + * text. + */ + private IContentProposal[] filterProposals( + IContentProposal[] proposals, String filterString) { + if (filterString.length() == 0) { + return proposals; + } + + // Check each string for a match. Use the string displayed to the + // user, not the proposal content. + ArrayList list = new ArrayList(); + for (int i = 0; i < proposals.length; i++) { + String string = getString(proposals[i]); + if (string.length() >= filterString.length() + && string.substring(0, filterString.length()) + .equalsIgnoreCase(filterString)) { + list.add(proposals[i]); + } + + } + return (IContentProposal[]) list.toArray(new IContentProposal[list + .size()]); + } + + Listener getTargetControlListener() { + if (targetControlListener == null) { + targetControlListener = new TargetControlListener(); + } + return targetControlListener; + } + } + + /** + * Flag that controls the printing of debug info. + */ + public static final boolean DEBUG = false; + + /** + * Indicates that a chosen proposal should be inserted into the field. + */ + public static final int PROPOSAL_INSERT = 1; + + /** + * Indicates that a chosen proposal should replace the entire contents of + * the field. + */ + public static final int PROPOSAL_REPLACE = 2; + + /** + * Indicates that the contents of the control should not be modified when a + * proposal is chosen. This is typically used when a client needs more + * specialized behavior when a proposal is chosen. In this case, clients + * typically register an IContentProposalListener so that they are notified + * when a proposal is chosen. + */ + public static final int PROPOSAL_IGNORE = 3; + + /** + * Indicates that there should be no filter applied as keys are typed in the + * popup. + */ + public static final int FILTER_NONE = 1; + + /** + * Indicates that a single character filter applies as keys are typed in the + * popup. + */ + public static final int FILTER_CHARACTER = 2; + + /** + * Indicates that a cumulative filter applies as keys are typed in the + * popup. That is, each character typed will be added to the filter. + * + * @deprecated As of 3.4, filtering that is sensitive to changes in the + * control content should be performed by the supplied + * {@link IContentProposalProvider}, such as that performed by + * {@link SimpleContentProposalProvider} + */ + public static final int FILTER_CUMULATIVE = 3; + + /* + * Set to true to use a Table with SWT.VIRTUAL. This is a + * workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=98585#c40 + * The corresponding SWT bug is + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=90321 + */ + private static final boolean USE_VIRTUAL = !Util.isMotif(); + + /* + * The delay before showing a secondary popup. + */ + private static final int POPUP_DELAY = 750; + + /* + * The character height hint for the popup. May be overridden by using + * setInitialPopupSize. + */ + private static final int POPUP_CHAR_HEIGHT = 10; + + /* + * The minimum pixel width for the popup. May be overridden by using + * setInitialPopupSize. + */ + private static final int POPUP_MINIMUM_WIDTH = 300; + + /* + * The pixel offset of the popup from the bottom corner of the control. + */ + private static final int POPUP_OFFSET = 3; + + /* + * Empty string. + */ + private static final String EMPTY = ""; //$NON-NLS-1$ + + /* + * The object that provides content proposals. + */ + private IContentProposalProvider proposalProvider; + + /* + * A label provider used to display proposals in the popup, and to extract + * Strings from non-String proposals. + */ + private ILabelProvider labelProvider; + + /* + * The control for which content proposals are provided. + */ + private Control control; + + /* + * The adapter used to extract the String contents from an arbitrary + * control. + */ + private IControlContentAdapter controlContentAdapter; + + /* + * The popup used to show proposals. + */ + private ContentProposalPopup popup; + + /* + * The keystroke that signifies content proposals should be shown. + */ + private KeyStroke triggerKeyStroke; + + /* + * The String containing characters that auto-activate the popup. + */ + private String autoActivateString; + + /* + * Integer that indicates how an accepted proposal should affect the + * control. One of PROPOSAL_IGNORE, PROPOSAL_INSERT, or PROPOSAL_REPLACE. + * Default value is PROPOSAL_INSERT. + */ + private int proposalAcceptanceStyle = PROPOSAL_INSERT; + + /* + * A boolean that indicates whether key events received while the proposal + * popup is open should also be propagated to the control. Default value is + * true. + */ + private boolean propagateKeys = true; + + /* + * Integer that indicates the filtering style. One of FILTER_CHARACTER, + * FILTER_CUMULATIVE, FILTER_NONE. + */ + private int filterStyle = FILTER_NONE; + + /* + * The listener we install on the control. + */ + private Listener controlListener; + + /* + * The list of IContentProposalListener listeners. + */ + private ListenerList proposalListeners = new ListenerList(); + + /* + * The list of IContentProposalListener2 listeners. + */ + private ListenerList proposalListeners2 = new ListenerList(); + + /* + * Flag that indicates whether the adapter is enabled. In some cases, + * adapters may be installed but depend upon outside state. + */ + private boolean isEnabled = true; + + /* + * The delay in milliseconds used when autoactivating the popup. + */ + private int autoActivationDelay = 0; + + /* + * A boolean indicating whether a keystroke has been received. Used to see + * if an autoactivation delay was interrupted by a keystroke. + */ + private boolean receivedKeyDown; + + /* + * The desired size in pixels of the proposal popup. + */ + private Point popupSize; + + /* + * The remembered position of the insertion position. Not all controls will + * restore the insertion position if the proposal popup gets focus, so we + * need to remember it. + */ + private int insertionPos = -1; + + /* + * The remembered selection range. Not all controls will restore the + * selection position if the proposal popup gets focus, so we need to + * remember it. + */ + private Point selectionRange = new Point(-1, -1); + + /* + * A flag that indicates that we are watching modify events + */ + private boolean watchModify = false; + + /** + * Construct a content proposal adapter that can assist the user with + * choosing content for the field. + * + * @param control + * the control for which the adapter is providing content assist. + * May not be null. + * @param controlContentAdapter + * the IControlContentAdapter used to obtain and + * update the control's contents as proposals are accepted. May + * not be null. + * @param proposalProvider + * the IContentProposalProvider used to obtain + * content proposals for this control, or null if + * no content proposal is available. + * @param keyStroke + * the keystroke that will invoke the content proposal popup. If + * this value is null, then proposals will be + * activated automatically when any of the auto activation + * characters are typed. + * @param autoActivationCharacters + * An array of characters that trigger auto-activation of content + * proposal. If specified, these characters will trigger + * auto-activation of the proposal popup, regardless of whether + * an explicit invocation keyStroke was specified. If this + * parameter is null, then only a specified + * keyStroke will invoke content proposal. If this parameter is + * null and the keyStroke parameter is + * null, then all alphanumeric characters will + * auto-activate content proposal. + */ + public ContentProposalAdapter(Control control, + IControlContentAdapter controlContentAdapter, + IContentProposalProvider proposalProvider, KeyStroke keyStroke, + char[] autoActivationCharacters) { + super(); + // We always assume the control and content adapter are valid. + Assert.isNotNull(control); + Assert.isNotNull(controlContentAdapter); + this.control = control; + this.controlContentAdapter = controlContentAdapter; + + // The rest of these may be null + this.proposalProvider = proposalProvider; + this.triggerKeyStroke = keyStroke; + if (autoActivationCharacters != null) { + this.autoActivateString = new String(autoActivationCharacters); + } + addControlListener(control); + } + + /** + * Get the control on which the content proposal adapter is installed. + * + * @return the control on which the proposal adapter is installed. + */ + public Control getControl() { + return control; + } + + /** + * Get the label provider that is used to show proposals. + * + * @return the {@link ILabelProvider} used to show proposals, or + * null if one has not been installed. + */ + public ILabelProvider getLabelProvider() { + return labelProvider; + } + + /** + * Return a boolean indicating whether the receiver is enabled. + * + * @return true if the adapter is enabled, and + * false if it is not. + */ + public boolean isEnabled() { + return isEnabled; + } + + /** + * Set the label provider that is used to show proposals. The lifecycle of + * the specified label provider is not managed by this adapter. Clients must + * dispose the label provider when it is no longer needed. + * + * @param labelProvider + * the (@link ILabelProvider} used to show proposals. + */ + public void setLabelProvider(ILabelProvider labelProvider) { + this.labelProvider = labelProvider; + } + + /** + * Return the proposal provider that provides content proposals given the + * current content of the field. A value of null indicates + * that there are no content proposals available for the field. + * + * @return the {@link IContentProposalProvider} used to show proposals. May + * be null. + */ + public IContentProposalProvider getContentProposalProvider() { + return proposalProvider; + } + + /** + * Set the content proposal provider that is used to show proposals. + * + * @param proposalProvider + * the {@link IContentProposalProvider} used to show proposals + */ + public void setContentProposalProvider( + IContentProposalProvider proposalProvider) { + this.proposalProvider = proposalProvider; + } + + /** + * Return the array of characters on which the popup is autoactivated. + * + * @return An array of characters that trigger auto-activation of content + * proposal. If specified, these characters will trigger + * auto-activation of the proposal popup, regardless of whether an + * explicit invocation keyStroke was specified. If this parameter is + * null, then only a specified keyStroke will invoke + * content proposal. If this value is null and the + * keyStroke value is null, then all alphanumeric + * characters will auto-activate content proposal. + */ + public char[] getAutoActivationCharacters() { + if (autoActivateString == null) { + return null; + } + return autoActivateString.toCharArray(); + } + + /** + * Set the array of characters that will trigger autoactivation of the + * popup. + * + * @param autoActivationCharacters + * An array of characters that trigger auto-activation of content + * proposal. If specified, these characters will trigger + * auto-activation of the proposal popup, regardless of whether + * an explicit invocation keyStroke was specified. If this + * parameter is null, then only a specified + * keyStroke will invoke content proposal. If this parameter is + * null and the keyStroke value is + * null, then all alphanumeric characters will + * auto-activate content proposal. + * + */ + public void setAutoActivationCharacters(char[] autoActivationCharacters) { + if (autoActivationCharacters == null) { + this.autoActivateString = null; + } else { + this.autoActivateString = new String(autoActivationCharacters); + } + } + + /** + * Set the delay, in milliseconds, used before any autoactivation is + * triggered. + * + * @return the time in milliseconds that will pass before a popup is + * automatically opened + */ + public int getAutoActivationDelay() { + return autoActivationDelay; + + } + + /** + * Set the delay, in milliseconds, used before autoactivation is triggered. + * + * @param delay + * the time in milliseconds that will pass before a popup is + * automatically opened + */ + public void setAutoActivationDelay(int delay) { + autoActivationDelay = delay; + + } + + /** + * Get the integer style that indicates how an accepted proposal affects the + * control's content. + * + * @return a constant indicating how an accepted proposal should affect the + * control's content. Should be one of PROPOSAL_INSERT, + * PROPOSAL_REPLACE, or PROPOSAL_IGNORE. + * (Default is PROPOSAL_INSERT). + */ + public int getProposalAcceptanceStyle() { + return proposalAcceptanceStyle; + } + + /** + * Set the integer style that indicates how an accepted proposal affects the + * control's content. + * + * @param acceptance + * a constant indicating how an accepted proposal should affect + * the control's content. Should be one of + * PROPOSAL_INSERT, PROPOSAL_REPLACE, + * or PROPOSAL_IGNORE + */ + public void setProposalAcceptanceStyle(int acceptance) { + proposalAcceptanceStyle = acceptance; + } + + /** + * Return the integer style that indicates how keystrokes affect the content + * of the proposal popup while it is open. + * + * @return a constant indicating how keystrokes in the proposal popup affect + * filtering of the proposals shown. FILTER_NONE + * specifies that no filtering will occur in the content proposal + * list as keys are typed. FILTER_CHARACTER specifies + * the content of the popup will be filtered by the most recently + * typed character. FILTER_CUMULATIVE is deprecated + * and no longer recommended. It specifies that the content of the + * popup will be filtered by a string containing all the characters + * typed since the popup has been open. The default is + * FILTER_NONE. + */ + public int getFilterStyle() { + return filterStyle; + } + + /** + * Set the integer style that indicates how keystrokes affect the content of + * the proposal popup while it is open. Popup-based filtering is useful for + * narrowing and navigating the list of proposals provided once the popup is + * open. Filtering of the proposals will occur even when the control content + * is not affected by user typing. Note that automatic filtering is not used + * to achieve content-sensitive filtering such as auto-completion. Filtering + * that is sensitive to changes in the control content should be performed + * by the supplied {@link IContentProposalProvider}. + * + * @param filterStyle + * a constant indicating how keystrokes received in the proposal + * popup affect filtering of the proposals shown. + * FILTER_NONE specifies that no automatic + * filtering of the content proposal list will occur as keys are + * typed in the popup. FILTER_CHARACTER specifies + * that the content of the popup will be filtered by the most + * recently typed character. FILTER_CUMULATIVE is + * deprecated and no longer recommended. It specifies that the + * content of the popup will be filtered by a string containing + * all the characters typed since the popup has been open. + */ + public void setFilterStyle(int filterStyle) { + this.filterStyle = filterStyle; + } + + /** + * Return the size, in pixels, of the content proposal popup. + * + * @return a Point specifying the last width and height, in pixels, of the + * content proposal popup. + */ + public Point getPopupSize() { + return popupSize; + } + + /** + * Set the size, in pixels, of the content proposal popup. This size will be + * used the next time the content proposal popup is opened. + * + * @param size + * a Point specifying the desired width and height, in pixels, of + * the content proposal popup. + */ + public void setPopupSize(Point size) { + popupSize = size; + } + + /** + * Get the boolean that indicates whether key events (including + * auto-activation characters) received by the content proposal popup should + * also be propagated to the adapted control when the proposal popup is + * open. + * + * @return a boolean that indicates whether key events (including + * auto-activation characters) should be propagated to the adapted + * control when the proposal popup is open. Default value is + * true. + */ + public boolean getPropagateKeys() { + return propagateKeys; + } + + /** + * Set the boolean that indicates whether key events (including + * auto-activation characters) received by the content proposal popup should + * also be propagated to the adapted control when the proposal popup is + * open. + * + * @param propagateKeys + * a boolean that indicates whether key events (including + * auto-activation characters) should be propagated to the + * adapted control when the proposal popup is open. + */ + public void setPropagateKeys(boolean propagateKeys) { + this.propagateKeys = propagateKeys; + } + + /** + * Return the content adapter that can get or retrieve the text contents + * from the adapter's control. This method is used when a client, such as a + * content proposal listener, needs to update the control's contents + * manually. + * + * @return the {@link IControlContentAdapter} which can update the control + * text. + */ + public IControlContentAdapter getControlContentAdapter() { + return controlContentAdapter; + } + + /** + * Set the boolean flag that determines whether the adapter is enabled. + * + * @param enabled + * true if the adapter is enabled and responding + * to user input, false if it is ignoring user + * input. + * + */ + public void setEnabled(boolean enabled) { + // If we are disabling it while it's proposing content, close the + // content proposal popup. + if (isEnabled && !enabled) { + if (popup != null) { + popup.close(); + } + } + isEnabled = enabled; + } + + /** + * Add the specified listener to the list of content proposal listeners that + * are notified when content proposals are chosen. + *

    + * + * @param listener + * the IContentProposalListener to be added as a listener. Must + * not be null. If an attempt is made to register + * an instance which is already registered with this instance, + * this method has no effect. + * + * @see org.eclipse.jface.fieldassist.IContentProposalListener + */ + public void addContentProposalListener(IContentProposalListener listener) { + proposalListeners.add(listener); + } + + /** + * Removes the specified listener from the list of content proposal + * listeners that are notified when content proposals are chosen. + *

    + * + * @param listener + * the IContentProposalListener to be removed as a listener. Must + * not be null. If the listener has not already + * been registered, this method has no effect. + * + * @since 3.3 + * @see org.eclipse.jface.fieldassist.IContentProposalListener + */ + public void removeContentProposalListener(IContentProposalListener listener) { + proposalListeners.remove(listener); + } + + /** + * Add the specified listener to the list of content proposal listeners that + * are notified when a content proposal popup is opened or closed. + *

    + * + * @param listener + * the IContentProposalListener2 to be added as a listener. Must + * not be null. If an attempt is made to register + * an instance which is already registered with this instance, + * this method has no effect. + * + * @since 3.3 + * @see org.eclipse.jface.fieldassist.IContentProposalListener2 + */ + public void addContentProposalListener(IContentProposalListener2 listener) { + proposalListeners2.add(listener); + } + + /** + * Remove the specified listener from the list of content proposal listeners + * that are notified when a content proposal popup is opened or closed. + *

    + * + * @param listener + * the IContentProposalListener2 to be removed as a listener. + * Must not be null. If the listener has not + * already been registered, this method has no effect. + * + * @since 3.3 + * @see org.eclipse.jface.fieldassist.IContentProposalListener2 + */ + public void removeContentProposalListener(IContentProposalListener2 listener) { + proposalListeners2.remove(listener); + } + + /* + * Add our listener to the control. Debug information to be left in until + * this support is stable on all platforms. + */ + private void addControlListener(Control control) { + if (DEBUG) { + System.out + .println("ContentProposalListener#installControlListener()"); //$NON-NLS-1$ + } + + if (controlListener != null) { + return; + } + controlListener = new Listener() { + public void handleEvent(Event e) { + if (!isEnabled) { + return; + } + + switch (e.type) { + case SWT.Traverse: + case SWT.KeyDown: + if (DEBUG) { + StringBuffer sb; + if (e.type == SWT.Traverse) { + sb = new StringBuffer("Traverse"); //$NON-NLS-1$ + } else { + sb = new StringBuffer("KeyDown"); //$NON-NLS-1$ + } + sb.append(" received by adapter"); //$NON-NLS-1$ + dump(sb.toString(), e); + } + // If the popup is open, it gets first shot at the + // keystroke and should set the doit flags appropriately. + if (popup != null) { + popup.getTargetControlListener().handleEvent(e); + if (DEBUG) { + StringBuffer sb; + if (e.type == SWT.Traverse) { + sb = new StringBuffer("Traverse"); //$NON-NLS-1$ + } else { + sb = new StringBuffer("KeyDown"); //$NON-NLS-1$ + } + sb.append(" after being handled by popup"); //$NON-NLS-1$ + dump(sb.toString(), e); + } + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=192633 + // If the popup is open and this is a valid character, we + // want to watch for the modified text. + if (propagateKeys && e.character != 0) + watchModify = true; + + return; + } + + // We were only listening to traverse events for the popup + if (e.type == SWT.Traverse) { + return; + } + + // The popup is not open. We are looking at keydown events + // for a trigger to open the popup. + if (triggerKeyStroke != null) { + // Either there are no modifiers for the trigger and we + // check the character field... + if ((triggerKeyStroke.getModifierKeys() == KeyStroke.NO_KEY && triggerKeyStroke + .getNaturalKey() == e.character) + || + // ...or there are modifiers, in which case the + // keycode and state must match + (triggerKeyStroke.getNaturalKey() == e.keyCode && ((triggerKeyStroke + .getModifierKeys() & e.stateMask) == triggerKeyStroke + .getModifierKeys()))) { + // We never propagate the keystroke for an explicit + // keystroke invocation of the popup + e.doit = false; + openProposalPopup(false); + return; + } + } + /* + * The triggering keystroke was not invoked. If a character + * was typed, compare it to the autoactivation characters. + */ + if (e.character != 0) { + if (autoActivateString != null) { + if (autoActivateString.indexOf(e.character) >= 0) { + autoActivate(); + } else { + // No autoactivation occurred, so record the key + // down as a means to interrupt any + // autoactivation that is pending due to + // autoactivation delay. + receivedKeyDown = true; + // watch the modify so we can close the popup in + // cases where there is no longer a trigger + // character in the content + watchModify = true; + } + } else { + // The autoactivate string is null. If the trigger + // is also null, we want to act on any modification + // to the content. Set a flag so we'll catch this + // in the modify event. + if (triggerKeyStroke == null) { + watchModify = true; + } + } + } else { + // A non-character key has been pressed. Interrupt any + // autoactivation that is pending due to autoactivation delay. + receivedKeyDown = true; + } + break; + + + // There are times when we want to monitor content changes + // rather than individual keystrokes to determine whether + // the popup should be closed or opened based on the entire + // content of the control. + // The watchModify flag ensures that we don't autoactivate if + // the content change was caused by something other than typing. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=183650 + case SWT.Modify: + if (allowsAutoActivate() && watchModify) { + if (DEBUG) { + dump("Modify event triggers popup open or close", e); //$NON-NLS-1$ + } + watchModify = false; + // We are in autoactivation mode, either for specific + // characters or for all characters. In either case, + // we should close the proposal popup when there is no + // content in the control. + if (isControlContentEmpty()) { + // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=192633 + closeProposalPopup(); + } else { + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=147377 + // Given that we will close the popup when there are + // no valid proposals, we must consider reopening it on any + // content change when there are no particular autoActivation + // characters + if (autoActivateString == null) { + autoActivate(); + } else { + // Autoactivation characters are defined, but this + // modify event does not involve one of them. See + // if any of the autoactivation characters are left + // in the content and close the popup if none remain. + if (!shouldPopupRemainOpen()) + closeProposalPopup(); + } + } + } + break; + default: + break; + } + } + + /** + * Dump the given events to "standard" output. + * + * @param who + * who is dumping the event + * @param e + * the event + */ + private void dump(String who, Event e) { + StringBuffer sb = new StringBuffer( + "--- [ContentProposalAdapter]\n"); //$NON-NLS-1$ + sb.append(who); + sb.append(" - e: keyCode=" + e.keyCode + hex(e.keyCode)); //$NON-NLS-1$ + sb.append("; character=" + e.character + hex(e.character)); //$NON-NLS-1$ + sb.append("; stateMask=" + e.stateMask + hex(e.stateMask)); //$NON-NLS-1$ + sb.append("; doit=" + e.doit); //$NON-NLS-1$ + sb.append("; detail=" + e.detail + hex(e.detail)); //$NON-NLS-1$ + sb.append("; widget=" + e.widget); //$NON-NLS-1$ + System.out.println(sb); + } + + private String hex(int i) { + return "[0x" + Integer.toHexString(i) + ']'; //$NON-NLS-1$ + } + }; + control.addListener(SWT.KeyDown, controlListener); + control.addListener(SWT.Traverse, controlListener); + control.addListener(SWT.Modify, controlListener); + + if (DEBUG) { + System.out + .println("ContentProposalAdapter#installControlListener() - installed"); //$NON-NLS-1$ + } + } + + /** + * Open the proposal popup and display the proposals provided by the + * proposal provider. If there are no proposals to be shown, do not show the + * popup. This method returns immediately. That is, it does not wait for the + * popup to open or a proposal to be selected. + * + * @param autoActivated + * a boolean indicating whether the popup was autoactivated. If + * false, a beep will sound when no proposals can be shown. + */ + private void openProposalPopup(boolean autoActivated) { + if (isValid()) { + if (popup == null) { + // Check whether there are any proposals to be shown. + recordCursorPosition(); // must be done before getting proposals + IContentProposal[] proposals = getProposals(); + if (proposals.length > 0) { + if (DEBUG) { + System.out.println("POPUP OPENED BY PRECEDING EVENT"); //$NON-NLS-1$ + } + recordCursorPosition(); + popup = new ContentProposalPopup(null, proposals); + popup.open(); + popup.getShell().addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent event) { + popup = null; + } + }); + internalPopupOpened(); + notifyPopupOpened(); + } else if (!autoActivated) { + getControl().getDisplay().beep(); + } + } + } + } + + /** + * Open the proposal popup and display the proposals provided by the + * proposal provider. This method returns immediately. That is, it does not + * wait for a proposal to be selected. This method is used by subclasses to + * explicitly invoke the opening of the popup. If there are no proposals to + * show, the popup will not open and a beep will be sounded. + */ + protected void openProposalPopup() { + openProposalPopup(false); + } + + /** + * Close the proposal popup without accepting a proposal. This method + * returns immediately, and has no effect if the proposal popup was not + * open. This method is used by subclasses to explicitly close the popup + * based on additional logic. + * + * @since 3.3 + */ + protected void closeProposalPopup() { + if (popup != null) { + popup.close(); + } + } + + /* + * A content proposal has been accepted. Update the control contents + * accordingly and notify any listeners. + * + * @param proposal the accepted proposal + */ + private void proposalAccepted(IContentProposal proposal) { + switch (proposalAcceptanceStyle) { + case (PROPOSAL_REPLACE): + setControlContent(proposal.getContent(), proposal + .getCursorPosition()); + break; + case (PROPOSAL_INSERT): + insertControlContent(proposal.getContent(), proposal + .getCursorPosition()); + break; + default: + // do nothing. Typically a listener is installed to handle this in + // a custom way. + break; + } + + // In all cases, notify listeners of an accepted proposal. + notifyProposalAccepted(proposal); + } + + /* + * Set the text content of the control to the specified text, setting the + * cursorPosition at the desired location within the new contents. + */ + private void setControlContent(String text, int cursorPosition) { + if (isValid()) { + // should already be false, but just in case. + watchModify = false; + controlContentAdapter.setControlContents(control, text, + cursorPosition); + } + } + + /* + * Insert the specified text into the control content, setting the + * cursorPosition at the desired location within the new contents. + */ + private void insertControlContent(String text, int cursorPosition) { + if (isValid()) { + // should already be false, but just in case. + watchModify = false; + // Not all controls preserve their selection index when they lose + // focus, so we must set it explicitly here to what it was before + // the popup opened. + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=127108 + // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=139063 + if (controlContentAdapter instanceof IControlContentAdapter2 + && selectionRange.x != -1) { + ((IControlContentAdapter2) controlContentAdapter).setSelection( + control, selectionRange); + } else if (insertionPos != -1) { + controlContentAdapter.setCursorPosition(control, insertionPos); + } + controlContentAdapter.insertControlContents(control, text, + cursorPosition); + } + } + + /* + * Check that the control and content adapter are valid. + */ + private boolean isValid() { + return control != null && !control.isDisposed() + && controlContentAdapter != null; + } + + /* + * Record the control's cursor position. + */ + private void recordCursorPosition() { + if (isValid()) { + IControlContentAdapter adapter = getControlContentAdapter(); + insertionPos = adapter.getCursorPosition(control); + // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=139063 + if (adapter instanceof IControlContentAdapter2) { + selectionRange = ((IControlContentAdapter2) adapter) + .getSelection(control); + } + + } + } + + /* + * Get the proposals from the proposal provider. Gets all of the proposals + * without doing any filtering. + */ + private IContentProposal[] getProposals() { + if (proposalProvider == null || !isValid()) { + return null; + } + if (DEBUG) { + System.out.println(">>> obtaining proposals from provider"); //$NON-NLS-1$ + } + int position = insertionPos; + if (position == -1) { + position = getControlContentAdapter().getCursorPosition( + getControl()); + } + String contents = getControlContentAdapter().getControlContents( + getControl()); + IContentProposal[] proposals = proposalProvider.getProposals(contents, + position); + return proposals; + } + + /** + * Autoactivation has been triggered. Open the popup using any specified + * delay. + */ + private void autoActivate() { + if (autoActivationDelay > 0) { + Runnable runnable = new Runnable() { + public void run() { + receivedKeyDown = false; + try { + Thread.sleep(autoActivationDelay); + } catch (InterruptedException e) { + } + if (!isValid() || receivedKeyDown) { + return; + } + getControl().getDisplay().syncExec(new Runnable() { + public void run() { + openProposalPopup(true); + } + }); + } + }; + Thread t = new Thread(runnable); + t.start(); + } else { + // Since we do not sleep, we must open the popup + // in an async exec. This is necessary because + // this method may be called in the middle of handling + // some event that will cause the cursor position or + // other important info to change as a result of this + // event occurring. + getControl().getDisplay().asyncExec(new Runnable() { + public void run() { + if (isValid()) { + openProposalPopup(true); + } + } + }); + } + } + + /* + * A proposal has been accepted. Notify interested listeners. + */ + private void notifyProposalAccepted(IContentProposal proposal) { + if (DEBUG) { + System.out.println("Notify listeners - proposal accepted."); //$NON-NLS-1$ + } + final Object[] listenerArray = proposalListeners.getListeners(); + for (int i = 0; i < listenerArray.length; i++) { + ((IContentProposalListener) listenerArray[i]) + .proposalAccepted(proposal); + } + } + + /* + * The proposal popup has opened. Notify interested listeners. + */ + private void notifyPopupOpened() { + if (DEBUG) { + System.out.println("Notify listeners - popup opened."); //$NON-NLS-1$ + } + final Object[] listenerArray = proposalListeners2.getListeners(); + for (int i = 0; i < listenerArray.length; i++) { + ((IContentProposalListener2) listenerArray[i]) + .proposalPopupOpened(this); + } + } + + /* + * The proposal popup has closed. Notify interested listeners. + */ + private void notifyPopupClosed() { + if (DEBUG) { + System.out.println("Notify listeners - popup closed."); //$NON-NLS-1$ + } + final Object[] listenerArray = proposalListeners2.getListeners(); + for (int i = 0; i < listenerArray.length; i++) { + ((IContentProposalListener2) listenerArray[i]) + .proposalPopupClosed(this); + } + } + + /** + * Returns whether the content proposal popup has the focus. This includes + * both the primary popup and any secondary info popup that may have focus. + * + * @return true if the proposal popup or its secondary info + * popup has the focus + * @since 3.4 + */ + public boolean hasProposalPopupFocus() { + return popup != null && popup.hasFocus(); + } + + /* + * Return whether the control content is empty + */ + private boolean isControlContentEmpty() { + return getControlContentAdapter().getControlContents(getControl()) + .length() == 0; + } + + /* + * The popup has just opened, but listeners have not yet + * been notified. Perform any cleanup that is needed. + */ + private void internalPopupOpened() { + // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=243612 + if (control instanceof Combo) { + ((Combo)control).setListVisible(false); + } + } + + /* + * Return whether a proposal popup should remain open. + * If it was autoactivated by specific characters, and + * none of those characters remain, then it should not remain + * open. This method should not be used to determine + * whether autoactivation has occurred or should occur, only whether + * the circumstances would dictate that a popup remain open. + */ + private boolean shouldPopupRemainOpen() { + // If we always autoactivate or never autoactivate, it should remain open + if (autoActivateString == null || autoActivateString.length() == 0) + return true; + String content = getControlContentAdapter().getControlContents(getControl()); + for (int i=0; i= 0) + return true; + } + return false; + } + + /* + * Return whether this adapter is configured for autoactivation, by + * specific characters or by any characters. + */ + private boolean allowsAutoActivate() { + return (autoActivateString != null && autoActivateString.length() > 0) // there are specific autoactivation chars supplied + || (autoActivateString == null && triggerKeyStroke == null); // we autoactivate on everything + } + + /** + * Sets focus to the proposal popup. If the proposal popup is not opened, + * this method is ignored. If the secondary popup has focus, focus is + * returned to the main proposal popup. + * + * @since 3.6 + */ + public void setProposalPopupFocus() { + if (isValid() && popup != null) + popup.getShell().setFocus(); + } + + /** + * Answers a boolean indicating whether the main proposal popup is open. + * + * @return true if the proposal popup is open, and + * false if it is not. + * + * @since 3.6 + */ + public boolean isProposalPopupOpen() { + if (isValid() && popup != null) + return true; + return false; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/IContentProposalListener2.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/IContentProposalListener2.java new file mode 100644 index 000000000..0b6f254d5 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/IContentProposalListener2.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.contentassist; + + +/** + * This interface is used to listen to additional notifications from a + * {@link ContentProposalAdapter}. + * @author Kris De Volder + * @author Nieraj Singh + * @since 3.3 + */ +public interface IContentProposalListener2 { + /** + * A content proposal popup has been opened for content proposal assistance. + * + * @param adapter + * the ContentProposalAdapter which is providing content proposal + * behavior to a control + */ + public void proposalPopupOpened(ContentProposalAdapter adapter); + + /** + * A content proposal popup has been closed. + * + * @param adapter + * the ContentProposalAdapter which is providing content proposal + * behavior to a control + */ + public void proposalPopupClosed(ContentProposalAdapter adapter); +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/IJavaContentAssistHandler.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/IJavaContentAssistHandler.java new file mode 100644 index 000000000..f19aa8ee8 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/IJavaContentAssistHandler.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.contentassist; + +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * This content assist handler handles a Java qualified name change entered in + * the text control specified by this handler. + *

    + * An option browse button can also be specified by this handler which may also + * trigger a qualified name change event, depending on whether the content + * assist adapter that invokes this handler supports browse button functionality + *

    + * @author Nieraj Singh + */ +public interface IJavaContentAssistHandler { + + /** + * Optional browse button that can be adapted to support Java type browsing. + * Clicking the browse button triggers a Java qualified name event. + * + * @return browse button to adapt, or null if no browse functionality is + * needed + */ + public Button getBrowseButtonControl(); + + /** + * Text control to adapt to support Java content assist as well as trigger + * Java qualified name change events. Must not be null. + * + * @return text control to adapt. Must not be null + */ + public Text getJavaTextControl(); + + /** + * Shell to use for the browse button, and content assist on the text + * control. Must not be null. + * + * @return shell to use for browse button and content assist. Cannot be + * null. + */ + public Shell getShell(); + + /** + * Concrete subclass get notified when a Java qualified name is entered in + * the given text control, or optionally, selected via the browse button + * press. + * + * @param qualifiedName + */ + public void handleJavaTypeSelection(String qualifiedName); + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/IProjectContentAssistProcessor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/IProjectContentAssistProcessor.java new file mode 100644 index 000000000..a8fb28f56 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/IProjectContentAssistProcessor.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.contentassist; + +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jface.text.contentassist.IContentAssistProcessor; + +/** + * @author Nieraj Singh + */ +public interface IProjectContentAssistProcessor extends IContentAssistProcessor { + + /** + * + * @return non-null project context used for content assist proposal + */ + public IJavaProject getProject(); + + /** + * This MUST be set before completion can obtain proposals. Must not be null + * @param non null java project set PRIOR to invoking content assist. + */ + public void setProject(IJavaProject project); +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/JavaContentAssistUIAdapter.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/JavaContentAssistUIAdapter.java new file mode 100644 index 000000000..a0de1768b --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/JavaContentAssistUIAdapter.java @@ -0,0 +1,516 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.contentassist; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.Assert; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.internal.corext.refactoring.StubTypeContext; +import org.eclipse.jdt.internal.corext.refactoring.TypeContextChecker; +import org.eclipse.jdt.internal.ui.dialogs.FilteredTypesSelectionDialog; +import org.eclipse.jdt.internal.ui.refactoring.contentassist.CompletionContextRequestor; +import org.eclipse.jdt.internal.ui.refactoring.contentassist.ControlContentAssistHelper; +import org.eclipse.jdt.internal.ui.refactoring.contentassist.JavaTypeCompletionProcessor; +import org.eclipse.jdt.ui.JavaElementLabelProvider; +import org.eclipse.jface.text.contentassist.IContentAssistProcessor; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.dialogs.ElementListSelectionDialog; +import org.eclipse.ui.dialogs.SearchPattern; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; +import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.JavaParameterDescriptor; +import org.springsource.ide.eclipse.commons.frameworks.core.internal.java.FrameworksJavaUtils; +import org.springsource.ide.eclipse.commons.frameworks.ui.internal.parameters.editors.IProjectSelectionChangeListener; + + +/** + * This adapter allows callers to adapt UI controls to support Java content + * assist and Java type browsing. + *

    + * The controls are specified by a handler passed in by some UI component that + * requires Java content assist and, optionally, Java type browsing. These UI + * components are typically wizard pages and dialogues. + *

    + *

    + * The adapter specifies content assist functionality that is added to a text + * control where Java type names are entered + *

    + *

    + * The adapter, also optionally specifies functionality that is added to a + * browse button. + *

    + *

    + * Subclasses can override the content assist getter to return content assist + * providers more specific to their domain. + *

    + *

    + * IMPORTANT: This content assist adapter is using an older Java content assist. + * As of 3.6, it is using the same content assist as found in the Java New Type + * wizard. A longer term TODO is to move away from the old Java content assist + * and adopt the newer platform content assist. + *

    + * @author Nieraj Singh + */ +public class JavaContentAssistUIAdapter implements + IProjectSelectionChangeListener { + + private IJavaContentAssistHandler javaContentAssistHandler; + private IJavaProject javaProject; + private JavaParameterDescriptor javaParameter; + + private IContentAssistProcessor processor; + + /** + * The java parameter contains the type of java to search for or use in + * content assist. Must not be null {@link IllegalArgumentException} is + * thrown if the parameter is null. + * + * + * @param javaParameter + * must not be null + */ + public JavaContentAssistUIAdapter(JavaParameterDescriptor javaParameter) { + Assert.isLegal(javaParameter != null); + this.javaParameter = javaParameter; + } + + public static boolean isNotifierValid(IJavaContentAssistHandler adapt) { + return adapt != null && adapt.getShell() != null + && adapt.getJavaTextControl() != null; + } + + /** + * Adapts a java int type to a Java search type. Returns -1 if it cannot + * adapt + * + * @param type + * @return Java search type or -1 if it cannot adapt + * @see IJavaSearchConstants + */ + private static int adaptToJavaSearchType(int type) { + switch (type) { + case JavaParameterDescriptor.FLAG_INTERFACE: + return IJavaSearchConstants.INTERFACE; + case JavaParameterDescriptor.FLAG_CLASS: + return IJavaSearchConstants.CLASS; + case JavaParameterDescriptor.FLAG_PACKAGE: + return IJavaSearchConstants.PACKAGE; + } + return -1; + } + + protected void applyContentAssist(Text text) { + processor = createContentAssistProcessor(); + if (processor != null) { + ControlContentAssistHelper.createTextContentAssistant(text, + processor); + reconfigureContentAssistProcessor(); + } + } + + /** + * Typically this method is not invoked by the subclass definition. It is is + * normally invoked by UI components that wish their UI controls adapted to + * the content assist and java type search mechanisms defined in this + * adapter. + * + * @param handler + */ + public void adapt(IJavaContentAssistHandler handler) { + this.javaContentAssistHandler = handler; + Assert.isLegal(isNotifierValid(handler)); + + Button browse = handler.getBrowseButtonControl(); + final Text text = handler.getJavaTextControl(); + + applyContentAssist(text); + + text.addModifyListener(new ModifyListener() { + + public void modifyText(ModifyEvent e) { + notifyTextSet(text.getText()); + } + + }); + + if (browse != null) { + browse.addSelectionListener(new SelectionAdapter() { + + public void widgetSelected(SelectionEvent e) { + browseButtonPressed(); + } + + public void widgetDefaultSelected(SelectionEvent e) { + browseButtonPressed(); + } + + }); + } + } + + /** + * Handles the browse button pressing. + */ + protected void browseButtonPressed() { + + if (javaProject == null || javaParameter == null) { + return; + } + + int type = javaParameter.getJavaElementType(); + BrowseButtonHandler browseHandler = null; + switch (type) { + case JavaParameterDescriptor.FLAG_CLASS: + case JavaParameterDescriptor.FLAG_INTERFACE: + browseHandler = new TypeBrowseButtonHandler( + javaContentAssistHandler, javaParameter, javaProject); + break; + case JavaParameterDescriptor.FLAG_PACKAGE: + browseHandler = new PackageBrowseButtonHandler( + javaContentAssistHandler, javaParameter, javaProject); + break; + } + + if (browseHandler != null) { + browseHandler.browseButtonPressed(); + } + + } + + protected void notifyTextSet(String text) { + javaContentAssistHandler.handleJavaTypeSelection(text); + } + + /** + * + * @return for content assist support, this must not be null. Otherwise no + * content assist support is added to the handler's text control + */ + protected IContentAssistProcessor createContentAssistProcessor() { + if (javaParameter == null) { + return null; + } + + int type = javaParameter.getJavaElementType(); + switch (type) { + case JavaParameterDescriptor.FLAG_CLASS: + case JavaParameterDescriptor.FLAG_INTERFACE: + return new JavaTypeCompletionProcessor(false, false, true); + case JavaParameterDescriptor.FLAG_PACKAGE: + return new JavaProjectPackageCompletionProcessor(); + } + + return null; + } + + protected void reconfigureContentAssistProcessor() { + + if (javaProject == null) { + return; + } + + if (processor instanceof JavaTypeCompletionProcessor) { + JavaTypeCompletionProcessor javaProcessor = (JavaTypeCompletionProcessor) processor; + IPackageFragmentRoot[] roots = FrameworksJavaUtils + .getFirstEncounteredSourcePackageFragmentRoots(javaProject); + // Search for the first default package fragment in the package + // fragment roots + // of the first encountered source class path entry in the Java + // project. + // It seems that this works even when looking for proposals outside + // of the package + // fragment or Java project. It is a working "hack", but this should + // be made extensible + // such that specific domains can provide their own context + if (roots != null) { + IPackageFragment fragment = null; + for (IPackageFragmentRoot root : roots) { + try { + IJavaElement[] members = root.getChildren(); + if (members != null) { + for (IJavaElement element : members) { + if (element instanceof IPackageFragment) { + IPackageFragment frag = (IPackageFragment) element; + if (frag.isDefaultPackage()) { + fragment = frag; + break; + } + } + } + } + if (fragment != null) { + break; + } + } catch (JavaModelException e) { + FrameworkCoreActivator.logError( + e.getLocalizedMessage(), e); + } + } + + if (fragment != null) { + final IPackageFragment packFrag = fragment; + javaProcessor + .setCompletionContextRequestor(new CompletionContextRequestor() { + public StubTypeContext getStubTypeContext() { + return getContentAssistTypeContext( + packFrag, + JavaTypeCompletionProcessor.DUMMY_CLASS_NAME); + } + }); + } + } + } else if (processor instanceof JavaProjectPackageCompletionProcessor) { + ((JavaProjectPackageCompletionProcessor) processor) + .setProject(javaProject); + } + } + + protected StubTypeContext getContentAssistTypeContext( + IPackageFragment frag, String typeName) { + if (javaParameter == null) { + return null; + } + + int type = javaParameter.getJavaElementType(); + switch (type) { + case JavaParameterDescriptor.FLAG_CLASS: + return TypeContextChecker.createSuperClassStubTypeContext(typeName, + null, frag); + case JavaParameterDescriptor.FLAG_INTERFACE: + return TypeContextChecker.createSuperInterfaceStubTypeContext( + typeName, null, frag); + } + return null; + + } + + /* + * (non-Javadoc) + * + * @see com.springsource.sts.frameworks.ui.internal.parameters.editors. + * IProjectSelectionChangeListener + * #projectSelectionChanged(org.eclipse.core.resources.IProject) + */ + public void projectSelectionChanged(IProject project) { + if (project == null) { + javaProject = null; + return; + } + this.javaProject = JavaCore.create(project); + reconfigureContentAssistProcessor(); + } + + public static abstract class BrowseButtonHandler { + private IJavaContentAssistHandler handler; + private JavaParameterDescriptor descriptor; + private IJavaProject project; + + /** + * + * @param handler cannot be null + * @param descriptor cannot be null + * @param project cannot be null + */ + public BrowseButtonHandler(IJavaContentAssistHandler handler, + JavaParameterDescriptor descriptor, IJavaProject project) { + this.handler = handler; + this.descriptor = descriptor; + this.project = project; + } + + protected IJavaContentAssistHandler getHandler() { + return handler; + } + + protected JavaParameterDescriptor getJavaParameterDescriptor() { + return descriptor; + } + + protected IJavaProject getJavaProject() { + return project; + } + + protected void setValueInTextControl(String value) { + Text text = getHandler().getJavaTextControl(); + if (text == null || text.isDisposed()) { + return; + } + text.setText(value); + } + + /** + * Set the value in the Text control and notify the handler that the + * value was set. + * @param value to set in text control and notify handler + */ + protected void setValue(String value) { + setValueInTextControl(value); + getHandler().handleJavaTypeSelection(value); + } + + abstract public void browseButtonPressed(); + + } + + public static class PackageBrowseButtonHandler extends BrowseButtonHandler { + + public PackageBrowseButtonHandler(IJavaContentAssistHandler handler, + JavaParameterDescriptor descriptor, IJavaProject project) { + super(handler, descriptor, project); + } + + public void browseButtonPressed() { + IPackageFragment[] projectFragments = getPackageFragments(); + if (projectFragments == null) { + return; + } + + Shell shell = getHandler().getShell(); + + ElementListSelectionDialog dialog = new ElementListSelectionDialog( + shell, new JavaElementLabelProvider( + JavaElementLabelProvider.SHOW_DEFAULT)); + dialog.setIgnoreCase(false); + dialog.setTitle(getJavaParameterDescriptor().getName()); + dialog.setMessage("Select a source package"); + dialog.setEmptyListMessage("No source packages to select in given project"); + dialog.setElements(projectFragments); + dialog.setHelpAvailable(false); + + if (dialog.open() == Window.OK) { + String dialogValue = ((IPackageFragment) dialog.getFirstResult()) + .getElementName(); + setValue(dialogValue); + } + } + + protected IPackageFragment[] getPackageFragments() { + IPackageFragmentRoot[] roots; + try { + roots = getJavaProject().getAllPackageFragmentRoots(); + if (roots == null) { + return null; + } + } catch (JavaModelException e1) { + return null; + } + + Text textControl = getHandler().getJavaTextControl(); + String pattern = textControl != null ? textControl.getText() : null; + + SearchPattern searchPattern = pattern != null ? new SearchPattern() + : null; + + if (searchPattern != null) { + searchPattern.setPattern(pattern); + } + + List packageFragments = new ArrayList(); + for (IPackageFragmentRoot root : roots) { + try { + // To filter out all other package fragments from + // dependencies, + // ONLY check for source root types. A similar thing is done + // for the Java new type wizard. If this needs to be changed + // comment out the CPE_SOURCE check below. + IClasspathEntry entry = root.getRawClasspathEntry(); + if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) { + continue; + } + IJavaElement[] children = root.getChildren(); + for (IJavaElement child : children) { + // Check for duplicates + if (child instanceof IPackageFragment) { + String name = ((IPackageFragment) child) + .getElementName(); + + // If no pattern specified, or it matches a pattern + // add the fragment. Do not add if a patter is specified + // but it does not match + if (searchPattern == null + || searchPattern.matches(name)) { + packageFragments.add((IPackageFragment) child); + } + } + } + } catch (JavaModelException e) { + // ignore + } + } + + return packageFragments + .toArray(new IPackageFragment[packageFragments.size()]); + + } + } + + public static class TypeBrowseButtonHandler extends BrowseButtonHandler { + + public TypeBrowseButtonHandler(IJavaContentAssistHandler handler, + JavaParameterDescriptor descriptor, IJavaProject project) { + super(handler, descriptor, project); + } + + private static final String TITLE = "Select a type"; + + public void browseButtonPressed() { + Text textControl = getHandler().getJavaTextControl(); + String pattern = textControl != null && !textControl.isDisposed()? textControl.getText() : null; + + Shell shell = getHandler().getShell(); + + int javaSearchType = adaptToJavaSearchType(getJavaParameterDescriptor() + .getJavaElementType()); + + if (javaSearchType == -1) { + return; + } + + IJavaElement[] elements = new IJavaElement[] { getJavaProject() }; + IJavaSearchScope scope = SearchEngine + .createJavaSearchScope(elements); + + FilteredTypesSelectionDialog dialog = new FilteredTypesSelectionDialog( + shell, false, null, scope, javaSearchType); + dialog.setTitle(getJavaParameterDescriptor().getName()); + dialog.setMessage(TITLE); + dialog.setInitialPattern(pattern); + + if (dialog.open() == Window.OK) { + IType type = (IType) dialog.getFirstResult(); + if (type != null) { + String qualifiedName = type.getFullyQualifiedName(); + setValue(qualifiedName); + } + } + } + + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/JavaProjectPackageCompletionProcessor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/JavaProjectPackageCompletionProcessor.java new file mode 100644 index 000000000..1414843ff --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/contentassist/JavaProjectPackageCompletionProcessor.java @@ -0,0 +1,240 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.contentassist; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.ui.JavaPlugin; +import org.eclipse.jdt.internal.ui.text.java.JavaCompletionProposal; +import org.eclipse.jdt.ui.JavaElementLabelProvider; +import org.eclipse.jdt.ui.PreferenceConstants; +import org.eclipse.jdt.ui.text.java.CompletionProposalComparator; +import org.eclipse.jface.contentassist.IContentAssistSubjectControl; +import org.eclipse.jface.contentassist.ISubjectControlContentAssistProcessor; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.contentassist.ICompletionProposal; +import org.eclipse.jface.text.contentassist.IContextInformation; +import org.eclipse.jface.text.contentassist.IContextInformationValidator; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.swt.graphics.Image; + +/** + * This may be a temporary content assist solution for STS. It uses the same + * deprecated infrastructure as the Java New Type wizard. However, if the Java + * new type wizard changes to platform content assist, then this completion + * processor should be deprecated, and STS should adopt the newer content assist + * framework. + *

    + * This package content assist only works PER single java project, and will ONLY find + * package fragments in a given Java project for package fragment roots that are + * of CPE_SOURCE kind. If this needs to be changed, the CPE_SOURCE check should + * be removed in the code below + *

    + * @author Nieraj Singh + */ +public class JavaProjectPackageCompletionProcessor implements + IProjectContentAssistProcessor, ISubjectControlContentAssistProcessor { + + private CompletionProposalComparator proposalComparator; + private ILabelProvider labelProvider; + private IJavaProject project; + + private char[] proposalAutoActivationSet; + + /** + * Creates a JavaProjectPackageCompletionProcessor. The + * completion context must be set via {@link #setProject(IJavaProject)}. + */ + public JavaProjectPackageCompletionProcessor() { + this(new JavaElementLabelProvider( + JavaElementLabelProvider.SHOW_SMALL_ICONS)); + } + + /** + * Creates a JavaPackageCompletionProcessor. The Processor uses + * the given ILabelProvider to show text and icons for the + * possible completions. + * + * @param labelProvider + * Used for the popups. + */ + public JavaProjectPackageCompletionProcessor(ILabelProvider labelProvider) { + proposalComparator = new CompletionProposalComparator(); + this.labelProvider = labelProvider; + } + + /* + * (non-Javadoc) + * + * @see com.springsource.sts.frameworks.ui.internal.contentassist. + * IProjectContentAssistProcessor#getProject() + */ + public IJavaProject getProject() { + return project; + } + + /* + * (non-Javadoc) + * + * @see com.springsource.sts.frameworks.ui.internal.contentassist. + * IProjectContentAssistProcessor + * #setProject(org.eclipse.jdt.core.IJavaProject) + */ + public void setProject(IJavaProject project) { + this.project = project; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor# + * computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int) + */ + public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, + int documentOffset) { + return null; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor# + * computeContextInformation(org.eclipse.jface.text.ITextViewer, int) + */ + public IContextInformation[] computeContextInformation(ITextViewer viewer, + int documentOffset) { + return null; + } + + /* + * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor# + * getCompletionProposalAutoActivationCharacters() + */ + public char[] getCompletionProposalAutoActivationCharacters() { + if (proposalAutoActivationSet == null) { + IPreferenceStore preferenceStore = JavaPlugin.getDefault() + .getPreferenceStore(); + String triggers = preferenceStore + .getString(PreferenceConstants.CODEASSIST_AUTOACTIVATION_TRIGGERS_JAVA); + proposalAutoActivationSet = triggers.toCharArray(); + } + return proposalAutoActivationSet; + } + + /* + * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor# + * getContextInformationAutoActivationCharacters() + */ + public char[] getContextInformationAutoActivationCharacters() { + return null; + } + + /* + * @see + * org.eclipse.jface.text.contentassist.IContentAssistProcessor#getErrorMessage + * () + */ + public String getErrorMessage() { + return null; + } + + /* + * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor# + * getContextInformationValidator() + */ + public IContextInformationValidator getContextInformationValidator() { + return null; + } + + /* + * @see ISubjectControlContentAssistProcessor#computeContextInformation( + * IContentAssistSubjectControl, int) + */ + public IContextInformation[] computeContextInformation( + IContentAssistSubjectControl contentAssistSubjectControl, + int documentOffset) { + return null; + } + + /* + * @see ISubjectControlContentAssistProcessor#computeCompletionProposals( + * IContentAssistSubjectControl, int) + */ + public ICompletionProposal[] computeCompletionProposals( + IContentAssistSubjectControl contentAssistSubjectControl, + int documentOffset) { + if (project == null) { + return null; + } + String input = contentAssistSubjectControl.getDocument().get(); + ICompletionProposal[] foundProposals = null; + + IPackageFragmentRoot[] roots; + try { + roots = project.getAllPackageFragmentRoots(); + if (roots == null) { + return null; + } + } catch (JavaModelException e1) { + return null; + } + + List proposals = new ArrayList(); + String prefix = input.substring(0, documentOffset); + for (IPackageFragmentRoot root : roots) { + try { + // To filter out all other package fragments from dependencies, + // ONLY check for source root types. A similar thing is done + // for the Java new type wizard. If this needs to be changed + // comment out the CPE_SOURCE check below. + IClasspathEntry entry = root.getRawClasspathEntry(); + if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) { + continue; + } + IJavaElement[] children = root.getChildren(); + for (IJavaElement child : children) { + // Check for duplicates + if (child instanceof IPackageFragment) { + IPackageFragment packageFragment = (IPackageFragment) child; + String packName = packageFragment.getElementName(); + if (packName.startsWith(prefix)) { + Image image = labelProvider + .getImage(packageFragment); + JavaCompletionProposal proposal = new JavaCompletionProposal( + packName, 0, input.length(), image, + labelProvider.getText(packageFragment), 0); + proposals.add(proposal); + } + } + + } + } catch (JavaModelException e) { + // no proposals for this package fragment root, skip to next + } + } + + foundProposals = (ICompletionProposal[]) proposals + .toArray(new ICompletionProposal[proposals.size()]); + + Arrays.sort(foundProposals, proposalComparator); + return foundProposals; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/icons/IIcon.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/icons/IIcon.java new file mode 100644 index 000000000..7b05dce9f --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/icons/IIcon.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.icons; + +/** + * Allows elements, like tree viewer or Project explorer elements, to have icons + * and be handled by a framework icon manager + * @author Nieraj Singh + */ +public interface IIcon { + + /** + * The icon location should be an full file system path or a platform URI + * pointing to an icon image file. (e.g., + * "platform:/plugin/org.grails.ide.eclipse.explorer/icons/full/obj16/controllers.gif" + * ) + * + * @return + */ + public String getIconLocation(); + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/icons/Icon.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/icons/Icon.java new file mode 100644 index 000000000..909c86134 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/icons/Icon.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.icons; + + +/** + * @author Nieraj Singh + */ +public class Icon implements IIcon { + + private String iconLocation; + + public Icon(String iconLocation) { + this.iconLocation = iconLocation; + } + + public String getIconLocation() { + return iconLocation; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/icons/IconManager.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/icons/IconManager.java new file mode 100644 index 000000000..3b9f8c697 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/icons/IconManager.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.icons; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.Image; +import org.springsource.ide.eclipse.commons.frameworks.ui.FrameworkUIActivator; + + +/** + * @author Nieraj Singh + */ +public class IconManager { + private Map icons = new HashMap(); + + public Image getIcon(IIcon key) { + if (key == null) { + return null; + } + + ImageDescriptor descriptor = icons.get(key); + if (descriptor == null) { + try { + String location = key.getIconLocation(); + URL url = new URL(location); + if (location != null) { + descriptor = ImageDescriptor.createFromURL(url); + icons.put(key, descriptor); + } + } catch (MalformedURLException e) { + FrameworkUIActivator.log(new Status(IStatus.ERROR, FrameworkUIActivator.PLUGIN_ID, "Invalid URL: " + key.getIconLocation())); + } + } + return descriptor != null ? descriptor.createImage() : null; + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/ConvertLegacyProjectAction.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/ConvertLegacyProjectAction.java new file mode 100644 index 000000000..2644a8af3 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/ConvertLegacyProjectAction.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.legacyconversion; + +import org.eclipse.jface.action.IAction; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.ui.IObjectActionDelegate; +import org.eclipse.ui.IWorkbenchPart; + +/** + * Convert all legacy maven projects in the workspace + * + * @author Andrew Eisenberg + * @since 2.8.0 + */ +public class ConvertLegacyProjectAction implements IObjectActionDelegate { + + public void run(IAction action) { + LegacyProjectsJob job = new LegacyProjectsJob(true); + job.schedule(); + } + + public void selectionChanged(IAction action, ISelection selection) { + // we don't care about the actual selection + } + + public void setActivePart(IAction action, IWorkbenchPart targetPart) { + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/LegacyProjectListener.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/LegacyProjectListener.java new file mode 100644 index 000000000..a5347f134 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/LegacyProjectListener.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.legacyconversion; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IResourceDeltaVisitor; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; +import org.springsource.ide.eclipse.commons.frameworks.core.legacyconversion.IConversionConstants; + +/** + * Listens for legacy STS projects appearing in the workspace + * + * @author Andrew Eisenberg + * @since 3.0.0 + */ +public class LegacyProjectListener implements IResourceChangeListener, IConversionConstants { + + public static final LegacyProjectListener LISTENER = new LegacyProjectListener(); + + private boolean shouldPerformCheck() { + return FrameworkCoreActivator.getDefault().getPreferenceStore().getBoolean(AUTO_CHECK_FOR_LEGACY_STS_PROJECTS); + } + + public void resourceChanged(IResourceChangeEvent event) { + if (event.getType() == IResourceChangeEvent.POST_CHANGE) { + List projects = getProjects(event.getDelta()); + if (!projects.isEmpty() && shouldPerformCheck()) { + LegacyProjectsJob job = new LegacyProjectsJob(projects, false); + job.schedule(); + } + } + } + + private List getProjects(IResourceDelta delta) { + final List projects = new ArrayList(); + try { + delta.accept(new IResourceDeltaVisitor() { + public boolean visit(IResourceDelta innerDelta) throws CoreException { + if (innerDelta.getKind() == IResourceDelta.ADDED && innerDelta.getResource().getType() == IResource.PROJECT) { + IProject project = (IProject) innerDelta.getResource(); + if (LegacyProjectsJob.isLegacyProject(project, false)) { + projects.add(project); + } + } + // only continue for the workspace root + return innerDelta.getResource().getType() == IResource.ROOT; + } + }); + } catch (CoreException e) { + FrameworkCoreActivator.getDefault().getLog().log(e.getStatus()); + } + return projects; + } + + public void dispose() { + ResourcesPlugin.getWorkspace().removeResourceChangeListener(LegacyProjectListener.LISTENER); + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/LegacyProjectsJob.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/LegacyProjectsJob.java new file mode 100644 index 000000000..4e1c132d8 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/LegacyProjectsJob.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.legacyconversion; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.progress.UIJob; +import org.springsource.ide.eclipse.commons.frameworks.core.legacyconversion.IConversionConstants; +import org.springsource.ide.eclipse.commons.frameworks.core.legacyconversion.LegacyProjectConverter; + +/** + * Checks entire workspace for legacy projects + * + * @author Andrew Eisenberg + * @author Leo Dos Santos + * @since 3.0.0 + */ +public class LegacyProjectsJob extends UIJob implements IConversionConstants { + + private final boolean warnIfNone; + private List legacyProjects; + + public LegacyProjectsJob(boolean warnIfNone) { + super("Legacy STS Project Checker"); //$NON-NLS-1$ + this.warnIfNone = warnIfNone; + } + public LegacyProjectsJob(List legacyProjects, boolean warnIfNone) { + this(warnIfNone); + this.legacyProjects = legacyProjects; + } + + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + monitor.beginTask("Checking for legacy STS projects", 100); //$NON-NLS-1$ + IStatus status = doCheck(monitor, getDisplay().getActiveShell()); + monitor.done(); + return status; + } + + private IStatus doCheck(IProgressMonitor monitor, Shell shell) { + if (monitor == null) { + monitor = new NullProgressMonitor(); + } + + SubMonitor sub = SubMonitor.convert(monitor, 100); + if (legacyProjects == null) { + legacyProjects = findLegacyProjects(); + } + sub.worked(30); + if (legacyProjects.size() > 0) { + LegacyProjectConverter converter = new LegacyProjectConverter(legacyProjects); + if (askToConvert(shell, converter)) { + return converter.convert(sub.newChild(70)); + } + } else if (warnIfNone && !LegacySTSChecker.NON_BLOCKING) { + MessageDialog.openInformation(shell, "No legacy projects found", "No legacy projects found."); //$NON-NLS-1$ //$NON-NLS-2$ + } + return Status.OK_STATUS; + } + + private List findLegacyProjects() { + List legacyProjectsList = new ArrayList(); + IProject[] allProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); + for (IProject project : allProjects) { + try { + if (isLegacyProject(project, true)) { + legacyProjectsList.add(project); + } + } catch (CoreException e) { + // shouldn't happen since we already know project is accessible + // don't want to use the regular logging mechanism since that may + // load the bundle. + e.printStackTrace(); + } + } + return legacyProjectsList; + } + + public boolean askToConvert(Shell shell, LegacyProjectConverter converter) { + if (LegacySTSChecker.NON_BLOCKING) { + return false; + } + + converter.setSelectedLegacyProjects(ListMessageDialog.openViewer(shell, converter.getAllLegacyProjects().toArray(new IProject[0]))); + return converter.getSelectedLegacyProjects() != null; + } + + public static boolean isLegacyProject(IProject project, boolean isWorkspaceMigration) throws CoreException{ + return project.isAccessible() && + ( + // only migrate grails projects at workspace migration time. + // imported grails projects are handled by the GrailsProjectVersionFixer + (project.hasNature(GRAILS_OLD_NATURE) && isWorkspaceMigration) || + (project.hasNature(ROO_OLD_NATURE) && needsRooPrefMigration(project)) || + //only migrate gradle projects at workspace migration time, GradleImportOperation deals with old natures etc. during project import. + (project.hasNature(GRADLE_OLD_NATURE) && isWorkspaceMigration) + ); + } + + private static boolean needsRooPrefMigration(IProject project) { + IFolder preferencesFolder = project.getFolder(".settings/"); //$NON-NLS-1$ + File settingsFile = preferencesFolder.getFile(ROO_NEW_PLUGIN_NAME + ".prefs").getLocation().toFile(); //$NON-NLS-1$ //$NON-NLS-2$ + return !settingsFile.exists(); + } + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/LegacySTSChecker.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/LegacySTSChecker.java new file mode 100644 index 000000000..d130b056d --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/LegacySTSChecker.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.legacyconversion; + +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.ui.IPerspectiveDescriptor; +import org.eclipse.ui.IPerspectiveRegistry; +import org.eclipse.ui.IStartup; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.progress.UIJob; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; +import org.springsource.ide.eclipse.commons.frameworks.core.legacyconversion.IConversionConstants; +import org.springsource.ide.eclipse.commons.frameworks.core.legacyconversion.LegacyWorkspaceConverter; +import org.springsource.ide.eclipse.commons.frameworks.ui.FrameworkUIActivator; + +/** + * Checks for legacy STS projects in the workspace at startup. + * Must be very careful not to accidentally load the rest of STS if + * no legacy projects are found + * + * @author Andrew Eisenberg + * @since 3.0.0 + */ +public class LegacySTSChecker implements IStartup, IConversionConstants { + // set to true during testing mode + public static boolean NON_BLOCKING = false; + private static final IPreferenceStore PREFERENCE_STORE = FrameworkCoreActivator.getDefault().getPreferenceStore(); + + /** + * This entry to the checker comes at the startup of the workbench + */ + public void earlyStartup() { + PREFERENCE_STORE.setDefault(AUTO_CHECK_FOR_LEGACY_STS_PROJECTS, true); + + if (shouldPerformProjectCheck()) { + Job job = new LegacyProjectsJob(false); + job.schedule(); + ResourcesPlugin.getWorkspace().addResourceChangeListener(LegacyProjectListener.LISTENER, IResourceChangeEvent.POST_CHANGE); + } + + if (shouldPerformWorkspaceMigration()) { + new UIJob("Convert legacy STS 2.x preferences") { //$NON-NLS-1$ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + + IStatus status = new LegacyWorkspaceConverter().convert(monitor); + IStatus status2 = new PerspectiveMigrator().migratePerspective(GRAILS_OLD_PERSPECTIVE_ID, GRAILS_NEW_PERSPECTIVE_ID, monitor); + MultiStatus statuses = new MultiStatus(FrameworkUIActivator.PLUGIN_ID, 0, new IStatus[] { status, status2 }, "Legacy workspace migration", null); + return statuses; + } + + }.schedule(); + } + } + + private boolean shouldPerformProjectCheck() { + return FrameworkCoreActivator.getDefault().getPreferenceStore().getBoolean(AUTO_CHECK_FOR_LEGACY_STS_PROJECTS); + } + + private boolean shouldPerformWorkspaceMigration() { + return ! PREFERENCE_STORE.getBoolean(LEGACY_MIGRATION_ALREADY_DONE); + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/ListMessageDialog.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/ListMessageDialog.java new file mode 100644 index 000000000..c7daf57c2 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/ListMessageDialog.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.legacyconversion; + +import org.eclipse.core.resources.IProject; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.MessageDialogWithToggle; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.viewers.CheckboxTableViewer; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.model.WorkbenchLabelProvider; +import org.springsource.ide.eclipse.commons.frameworks.core.FrameworkCoreActivator; +import org.springsource.ide.eclipse.commons.frameworks.core.legacyconversion.IConversionConstants; + +/** + * + * @author Andrew Eisenberg + * @since 3.0.0 + */ +public class ListMessageDialog extends MessageDialogWithToggle implements IConversionConstants { + + class TableContentProvider implements IStructuredContentProvider { + public Object[] getElements(Object inputElement) { + if (inputElement instanceof IProject[]) { + return (IProject[]) inputElement; + } + return null; + } + + public void dispose() { } + public void inputChanged(Viewer viewer2, Object oldInput, Object newInput) { } + } + private static final IPreferenceStore PREFERENCE_STORE = FrameworkCoreActivator.getDefault().getPreferenceStore(); + private static final String PREFERENCE_QUESTION = "Don't show this dialog again."; //$NON-NLS-1$ + private static final String TITLE = "Should convert legacy STS projects?"; //$NON-NLS-1$ + + private final IProject[] legacyProjects; + private IProject[] checkedLegacyProjects; + + private CheckboxTableViewer viewer; + + /** + * Opens the legacy maven project conversion dialog focusing on the selected projects + * @param legacyProjects + * @return + */ + public static IProject[] openViewer(Shell shell, IProject[] legacyProjects) { + ListMessageDialog dialog = new ListMessageDialog(shell, legacyProjects); + int res = dialog.open(); + PREFERENCE_STORE.setValue(AUTO_CHECK_FOR_LEGACY_STS_PROJECTS, ! dialog.getToggleState()); + if (res == IDialogConstants.YES_ID) { + return dialog.getAllChecked(); + } else { + return null; + } + } + + @Override + protected void buttonPressed(int buttonId) { + if (buttonId == IDialogConstants.YES_ID) { + Object[] checkedElements = viewer.getCheckedElements(); + checkedLegacyProjects = new IProject[checkedElements.length]; + System.arraycopy(checkedElements, 0, checkedLegacyProjects, 0, checkedElements.length); + + // don't want to do workspace preferences again + PREFERENCE_STORE.setValue(LEGACY_MIGRATION_ALREADY_DONE, true); + } + super.buttonPressed(buttonId); + } + + @Override + protected boolean isResizable() { + return true; + } + + public ListMessageDialog(Shell shell, IProject[] legacyProjects) { + super(shell, TITLE, null, createMessage(legacyProjects), QUESTION, new String[] { IDialogConstants.YES_LABEL, + IDialogConstants.NO_LABEL }, 0, PREFERENCE_QUESTION, + PREFERENCE_STORE.getBoolean(AUTO_CHECK_FOR_LEGACY_STS_PROJECTS)); + this.legacyProjects = legacyProjects; + } + + protected Control createCustomArea(Composite parent) { + ((GridLayout) parent.getLayout()).numColumns = 2; + ((GridLayout) parent.getLayout()).makeColumnsEqualWidth = false; + + viewer = CheckboxTableViewer.newCheckList(parent, SWT.BORDER); + GridData gd = new GridData(GridData.FILL_BOTH); + gd.heightHint = 150; + gd.verticalSpan = 2; + viewer.getTable().setLayoutData(gd); + viewer.setContentProvider(new TableContentProvider()); + viewer.setLabelProvider(new WorkbenchLabelProvider()); + viewer.setInput(legacyProjects); + viewer.setAllChecked(true); + applyDialogFont(viewer.getControl()); + createButton(parent, "Select all", new SelectionAdapter() { //$NON-NLS-1$ + @Override + public void widgetSelected(SelectionEvent e) { + viewer.setAllChecked(true); + } + }); + createButton(parent, "Select none", new SelectionAdapter() { //$NON-NLS-1$ + @Override + public void widgetSelected(SelectionEvent e) { + viewer.setAllChecked(false); + } + }); + + return viewer.getControl(); + } + + protected Button createButton(Composite parent, String label, SelectionListener listener) { + return createButton(parent, label, SWT.PUSH, listener); + } + + protected Button createButton(Composite parent, String label, int style, SelectionListener listener) { + Button button= new Button(parent, SWT.PUSH); + button.setFont(parent.getFont()); + button.setText(label); + button.addSelectionListener(listener); + GridData gd= new GridData(); + gd.horizontalAlignment= GridData.FILL; + gd.grabExcessHorizontalSpace= false; + gd.verticalAlignment= GridData.BEGINNING; + gd.widthHint = 100; + + button.setLayoutData(gd); + + return button; + } + + IProject[] getAllChecked() { + return checkedLegacyProjects; + } + + private static String createMessage(IProject[] allLegacyProjects) { + StringBuilder sb = new StringBuilder(); + if (allLegacyProjects.length > 1) { + sb.append("The following legacy STS projects have been found:\n"); //$NON-NLS-1$ + } else { + sb.append("The following legacy STS project has been found:\n"); //$NON-NLS-1$ + } + if (allLegacyProjects.length > 1) { + sb.append("\n** These projects may not compile until they are upgraded to STS 3.0. **\n\n"); //$NON-NLS-1$ + } else { + sb.append("\n** This project may not compile until it is upgraded to STS 3.0. **\n\n"); //$NON-NLS-1$ + } + sb.append("Do you want to upgrade now?\n" + //$NON-NLS-1$ + "You can choose to upgrade later by going to:\n" + //$NON-NLS-1$ + "Project -> Configure -> Convert legacy STS projects..."); //$NON-NLS-1$ + return sb.toString(); + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/PerspectiveMigrator.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/PerspectiveMigrator.java new file mode 100644 index 000000000..be361fc70 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/legacyconversion/PerspectiveMigrator.java @@ -0,0 +1,85 @@ +/* + * Copyright 2011 SpringSource, a division of VMware, Inc + * + * andrew - Initial API and implementation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.legacyconversion; + +import java.lang.reflect.Method; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.ui.IPerspectiveDescriptor; +import org.eclipse.ui.IPerspectiveRegistry; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.internal.WorkbenchPage; +import org.springsource.ide.eclipse.commons.frameworks.core.legacyconversion.IConversionConstants; +import org.springsource.ide.eclipse.commons.frameworks.ui.FrameworkUIActivator; + +/** + * Migrates legacy perspectives to new perspectives. + * + * Actually, we're having a bit of trouble removing the persective from the Perspective bar in the UI, but + * this ensures that the new perspective is opened if necessary. + * @author Andrew Eisenberg + * @since 3.0.0 + */ +public class PerspectiveMigrator { + public IStatus migratePerspective(String oldPerspectiveId, String newPerspectiveId, IProgressMonitor monitor) { + try { + monitor = SubMonitor.convert(monitor, "Migrating legacy perspectives", 3); + IPerspectiveRegistry registry = PlatformUI.getWorkbench().getPerspectiveRegistry(); + monitor.worked(1); + IWorkbenchPage page = null; + try { + page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); + } catch (NullPointerException e) { + // something wasn't initialized...ignore + } + if (page != null) { + if (page.getPerspective() == null || page.getPerspective().getId().equals(oldPerspectiveId)) { + IPerspectiveDescriptor newPerspective = registry.findPerspectiveWithId(newPerspectiveId); + page.setPerspective(newPerspective); + IPerspectiveDescriptor oldPerspective = registry.findPerspectiveWithId(oldPerspectiveId); + monitor.worked(1); + if (oldPerspective != null) { + page.closePerspective(oldPerspective, false, false); + registry.deletePerspective(oldPerspective); + + } + } + // Actually, there is no mechanism to close this kind of perspective + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=381473 + if (page instanceof WorkbenchPage) { + try { + Method closePerspectiveMethod = WorkbenchPage.class.getDeclaredMethod("closePerspective", IPerspectiveDescriptor.class, String.class, boolean.class, boolean.class); + closePerspectiveMethod.invoke(page, null, IConversionConstants.GRAILS_OLD_PERSPECTIVE_ID, true, false); + } catch (Exception e) { + // this method doesn't exist on e37. OK to ignore +// FrameworkUIActivator.getDefault().getLog().log(new Status(IStatus.INFO, FrameworkUIActivator.PLUGIN_ID, "Cannot use reflection to close legacy perspective on Eclipse 3.7.", e)); + } + } + } + monitor.worked(1); + monitor.done(); + return new Status(IStatus.OK, FrameworkUIActivator.PLUGIN_ID, "Migrate legacy perspectives."); + } catch (Exception e) { + return new Status(IStatus.ERROR, FrameworkUIActivator.PLUGIN_ID, "Problem migrating legacy perspectives.", e); + } + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/parameters/editors/AbstractParameterEditor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/parameters/editors/AbstractParameterEditor.java new file mode 100644 index 000000000..7d0bcacf0 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/parameters/editors/AbstractParameterEditor.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.parameters.editors; + +import java.util.ArrayList; +import java.util.List; + +import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.ICommandParameter; +import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.ICommandParameterDescriptor; + + +/** + * @author Nieraj Singh + * @author Christian Dupuis + */ +public abstract class AbstractParameterEditor implements IParameterEditor, + IUIChangeListener { + + private ICommandParameter parameter; + private List listeners; + private boolean requiresLabel; + + public AbstractParameterEditor(ICommandParameter parameter, boolean requiresLabel) { + this.parameter = parameter; + this.requiresLabel = requiresLabel; + } + + public boolean requiresParameterNameLabel() { + return requiresLabel; + } + + public IUIChangeListener addUIChangeListener(IUIChangeListener listener) { + if (listener == null) { + return null; + } + if (listeners == null) { + listeners = new ArrayList(); + } + + if (listeners.contains(listener)) { + return null; + } + + listeners.add(listener); + + return listener; + } + + public IUIChangeListener removeUIChangeListener(IUIChangeListener listener) { + if (listeners != null && listeners.remove(listener)) { + return listener; + } + return null; + } + + public ICommandParameterDescriptor getParameterDescriptor() { + return parameter.getParameterDescriptor(); + } + + public ICommandParameter getParameter() { + return parameter; + } + + protected void setParameterValueAndNotifyClear(Object value) { + setParameterValue(value, UIEvent.VALUE_SET | UIEvent.CLEAR_VALUE_EVENT); + } + + protected void setParameterValue(Object value, int eventType) { + ICommandParameter parameter = getParameter(); + if (parameter != null) { + parameter.setValue(value); + notifyListeners(value, eventType); + } + } + + protected void notifyListeners(Object eventData, int type) { + + UIEvent event = new UIEvent(eventData, this, type); + if (listeners != null) { + for (IUIChangeListener listener : listeners) { + listener.handleUIEvent(event); + } + } + } + + + public void handleUIEvent(UIEvent event) { + if ((event.getType() & UIEvent.CLEAR_VALUE_EVENT) == UIEvent.CLEAR_VALUE_EVENT) { + setParameterValue(null, UIEvent.VALUE_SET); + clearControls(); + } + } + + /** + * Subclasses must implement when a clear control event is received, to + * clear all values in any editor control where values are set + */ + abstract protected void clearControls(); + +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/parameters/editors/BaseParameterEditor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/parameters/editors/BaseParameterEditor.java new file mode 100644 index 000000000..e80143ff3 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/parameters/editors/BaseParameterEditor.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.parameters.editors; + +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.ICommandParameter; + + +/** + * Creates an editor with a 2 column composite, where the first column has a + * text control and the second allows for further controls in that same row + * (i.e. by having a subclass add a nested composite in the second column with + * additional controls that appear in that same row). + * @author Nieraj Singh + * @author Christian Dupuis + */ +public class BaseParameterEditor extends AbstractParameterEditor { + + private Text commandValueText; + boolean firstFocus = true; + protected static final int NUMBER_OF_COLUMNS = 2; + + private int uiEventType = UIEvent.VALUE_SET | UIEvent.CLEAR_VALUE_EVENT; + + public BaseParameterEditor(ICommandParameter parameter, + boolean requiresLabel) { + super(parameter, requiresLabel); + } + + protected int numberOfTextControlCompositeColumns() { + return NUMBER_OF_COLUMNS; + } + + public Composite createControls(Composite parent) { + + Composite textControlComposite = new Composite(parent, SWT.NONE); + GridLayoutFactory.fillDefaults() + .numColumns(numberOfTextControlCompositeColumns()) + .margins(0, 0).applyTo(textControlComposite); + GridDataFactory.fillDefaults().grab(true, false) + .applyTo(textControlComposite); + + commandValueText = new Text(textControlComposite, SWT.BORDER); + + commandValueText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, + false)); + + // Set the default value BEFORE adding the modify listener + Object defaultValue = getParameter().getValue(); + if (defaultValue instanceof String) { + commandValueText.setText((String) defaultValue); + } + + commandValueText.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + notifyTextChange(commandValueText.getText()); + } + }); + + commandValueText.addFocusListener(new FocusListener() { + + public void focusLost(FocusEvent e) { + // Nothing + } + + public void focusGained(FocusEvent e) { + // Notify the wizard that focus has been set the first + // time to allow the wizard to output any error messages + if (firstFocus) { + notifyTextChange(commandValueText.getText()); + firstFocus = false; + } + } + }); + + return textControlComposite; + } + + /** + * Text control for the base parameter. It is assumed that the text control + * uses GridLayout and GridData + * + * @return text control, or null if it hasn't been created. Users should + * check null/disposed condition when invoking this method + */ + protected Text getTextControl() { + return commandValueText; + } + + protected void notifyTextChange(String text) { + setParameterValue(text, uiEventType); + // Reset the event type + uiEventType = UIEvent.VALUE_SET | UIEvent.CLEAR_VALUE_EVENT; + } + + protected void clearControls() { + if (commandValueText != null && !commandValueText.isDisposed()) { + uiEventType = UIEvent.VALUE_SET; + commandValueText.setText(""); + } + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/parameters/editors/BooleanParameterEditor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/parameters/editors/BooleanParameterEditor.java new file mode 100644 index 000000000..4395c5168 --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/parameters/editors/BooleanParameterEditor.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.parameters.editors; + +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.ICommandParameter; +import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.ICommandParameterDescriptor; + + +/** + * @author Nieraj Singh + * @author Christian Dupuis + */ +public class BooleanParameterEditor extends AbstractParameterEditor implements + IParameterEditor { + + private Button boolButton; + + public BooleanParameterEditor(ICommandParameter command) { + super(command, false); + } + + public Composite createControls(Composite parent) { + Composite baseCommandArea = new Composite(parent, SWT.NONE); + GridLayoutFactory.fillDefaults().numColumns(1).margins(0, 0) + .applyTo(baseCommandArea); + GridDataFactory.fillDefaults().grab(false, false) + .applyTo(baseCommandArea); + + final ICommandParameterDescriptor parameter = getParameterDescriptor(); + + String parameterName = parameter.getName(); + + // Use the any set value. Typically should always be set to the default + // value + Object defaultValue = getParameter().getValue(); + boolean valueToSet = (defaultValue instanceof Boolean) ? ((Boolean) defaultValue) + .booleanValue() : false; + + boolButton = new Button(baseCommandArea, SWT.CHECK); + // Note that the widget text may change if the wizard adds mneumonics + // for keyboard shortcuts + // therefore store the actual name as data as well and rely on the + // widget data as opposed + // to the text to get the parameter name + boolButton.setText(parameterName); + boolButton.setData(parameterName); + boolButton.setSelection(valueToSet); + boolButton.setToolTipText(parameter.getDescription()); + + GridDataFactory.fillDefaults().grab(false, false).applyTo(boolButton); + + boolButton.addSelectionListener(new SelectionAdapter() { + + public void widgetSelected(SelectionEvent e) { + setParameterValueAndNotifyClear(new Boolean(boolButton + .getSelection())); + } + + }); + return baseCommandArea; + } + + protected void clearControls() { + if (boolButton != null && !boolButton.isDisposed()) { + boolButton.setSelection(false); + } + } + + public Button getButtonControl() { + return boolButton; + } +} diff --git a/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/parameters/editors/ComboParameterEditor.java b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/parameters/editors/ComboParameterEditor.java new file mode 100644 index 000000000..34aea2a7e --- /dev/null +++ b/eclipse-language-servers/org.springsource.ide.eclipse.commons.frameworks.ui/src/org/springsource/ide/eclipse/commons/frameworks/ui/internal/parameters/editors/ComboParameterEditor.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2012 Pivotal Software, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal Software, Inc. - initial API and implementation + *******************************************************************************/ +package org.springsource.ide.eclipse.commons.frameworks.ui.internal.parameters.editors; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.ComboParameterDescriptor; +import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.ICommandParameter; +import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.ICommandParameterDescriptor; +import org.springsource.ide.eclipse.commons.frameworks.core.internal.commands.ParameterKind; +import org.springsource.ide.eclipse.commons.frameworks.ui.internal.swt.SWTFactory; + + +/** + * @author Nieraj Singh + */ +public class ComboParameterEditor extends AbstractParameterEditor { + + public static final int DEFAULT_COMBO_BOX_LIMIT = 5; + protected Map radialButtons; + protected Combo combo; + + public ComboParameterEditor(ICommandParameter parameter, + boolean requiresLabel) { + super(parameter, requiresLabel); + } + + public Composite createControls(Composite parent) { + ICommandParameterDescriptor parameter = getParameterDescriptor(); + if (parameter == null + || parameter.getParameterKind() != ParameterKind.COMBO) { + return null; + } + + ComboParameterDescriptor comboParameter = (ComboParameterDescriptor) parameter; + + String[] values = comboParameter.getSelectionValues(); + + + if (values == null || values.length == 0) { + return null; + } + + int size = values.length; + + if (size <= getComboBoxLimit()) { + createRadialButtons(parent, comboParameter); + } else { + createCombo(parent, comboParameter); + } + + // based on the number of arguments create either a combo or a dropdown + return null; + } + + protected int getComboBoxLimit() { + return DEFAULT_COMBO_BOX_LIMIT; + } + + protected Control createCombo(Composite parent, + ComboParameterDescriptor parameter) { + + String[] values = parameter.getSelectionValues(); + + combo = new Combo(parent, SWT.DROP_DOWN | SWT.READ_ONLY); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER) + .grab(false, false).applyTo(combo); + + combo.setItems(values); + + String defaultValue = (String) getParameter().getValue(); + if (defaultValue != null) { + for (int i = 0; i < values.length; i++) { + if (defaultValue.equals(values[i])) { + combo.select(i); + break; + } + } + } + + combo.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + setParameterValueAndNotifyClear(combo.getItem(combo + .getSelectionIndex())); + } + }); + + return combo; + } + + protected Control createRadialButtons(Composite parent, + ComboParameterDescriptor parameter) { + + Composite buttonArea = new Composite(parent, SWT.NONE); + + GridLayoutFactory.fillDefaults().numColumns(getComboBoxLimit()) + .equalWidth(true).applyTo(buttonArea); + GridDataFactory.fillDefaults().grab(true, false).applyTo(buttonArea); + radialButtons = new HashMap(); + String[] values = parameter.getSelectionValues(); + + // if there is a default value, set it + String defaultValue = (String) getParameter().getValue(); + for (String value : values) { + final Button button = SWTFactory.createRadialButton(buttonArea, + value, false); + + if (button != null) { + button.setData(value); + + if (value.equals(defaultValue)) { + button.setSelection(true); + } + button.addSelectionListener(new SelectionAdapter() { + + public void widgetSelected(SelectionEvent e) { + setParameterValueAndNotifyClear(button.getData()); + } + + }); + radialButtons.put(value, button); + } + } + return buttonArea; + } + + protected void clearControls() { + if (combo != null && !combo.isDisposed()) { + combo.deselectAll(); + } else if (radialButtons != null) { + Collection