commit 7d9a054bfeef39e80ac614cdda53e33371a9dd5b Author: James Peachey Date: Fri May 31 09:29:25 2024 -0400 Initial import of metadata code moved from crucible-crust. diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..7a1cf40 --- /dev/null +++ b/.classpath @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/.factorypath b/.factorypath new file mode 100644 index 0000000..610dea5 --- /dev/null +++ b/.factorypath @@ -0,0 +1,2 @@ + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/.project b/.project new file mode 100644 index 0000000..5ec67ee --- /dev/null +++ b/.project @@ -0,0 +1,24 @@ + + + jsqrl + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.jdt.core.javanature + org.apache.ivyde.eclipse.ivynature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..58acccd --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +encoding//Users/steelrj1/git_pristine/saavtk=UTF-8 +encoding/=UTF-8 +encoding/src=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..d176ef7 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,513 @@ +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.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=16 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=16 +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.APILeak=warning +org.eclipse.jdt.core.compiler.problem.annotatedTypeArgumentToUnannotated=info +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.enablePreviewFeatures=disabled +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=disabled +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=warning +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=ignore +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=ignore +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.reportPreviewFeatures=warning +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.suppressWarningsNotFullyAnalysed=info +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning +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=ignore +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning +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.release=enabled +org.eclipse.jdt.core.compiler.source=16 +org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false +org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 +org.eclipse.jdt.core.formatter.align_selector_in_method_invocation_on_expression_first_line=false +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false +org.eclipse.jdt.core.formatter.align_with_spaces=false +org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_enum_constant=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_field=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_local_variable=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_method=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_package=49 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_parameter=0 +org.eclipse.jdt.core.formatter.alignment_for_annotations_on_type=49 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=48 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=0 +org.eclipse.jdt.core.formatter.alignment_for_assertion_message=0 +org.eclipse.jdt.core.formatter.alignment_for_assignment=16 +org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=0 +org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=48 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_arrow=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_switch_case_with_colon=0 +org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_module_statements=0 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16 +org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=0 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_permitted_types_in_type_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_record_components=0 +org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0 +org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=0 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=0 +org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0 +org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_record_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_switch_case_with_arrow=0 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_type_annotations=0 +org.eclipse.jdt.core.formatter.alignment_for_type_arguments=0 +org.eclipse.jdt.core.formatter.alignment_for_type_parameters=0 +org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=0 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=0 +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_statement_group_in_switch=0 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=next_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=next_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=next_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=next_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_record_constructor=next_line +org.eclipse.jdt.core.formatter.brace_position_for_record_declaration=next_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=next_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=next_line +org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=false +org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false +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.count_line_length_from_starting_position=true +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=true +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.indent_tag_description=false +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_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=132 +org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +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.disabling_tag=@formatter\:off +org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=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_record_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_annotation_on_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +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_after_type_annotation=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=insert +org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=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_bitwise_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=do not 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_permitted_types=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_record_components=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions=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_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_not_operator=do not 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_record_declaration=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_try=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_relational_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case=insert +org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=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_bitwise_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_record_declaration=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_try=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_permitted_types=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_record_components=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_switch_case_expressions=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_lambda_arrow=insert +org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=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_record_constructor=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_record_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_record_declaration=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_try=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_relational_operator=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_semicolon_in_try_resources=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=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.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=false +org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_if_empty +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_enum_constant_declaration_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_record_constructor_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_record_declaration_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=false +org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false +org.eclipse.jdt.core.formatter.keep_switch_body_block_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_switch_case_with_arrow_on_one_line=one_line_never +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_if_empty +org.eclipse.jdt.core.formatter.lineSplit=132 +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_after_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_record_declaration=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines +org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=space +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.text_block_indentation=0 +org.eclipse.jdt.core.formatter.use_on_off_tags=false +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true +org.eclipse.jdt.core.formatter.wrap_before_assertion_message_operator=true +org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false +org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true +org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true +org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true +org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true +org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true +org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true +org.eclipse.jdt.core.formatter.wrap_before_switch_case_arrow_operator=false +org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true +org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..65198b6 --- /dev/null +++ b/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,144 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_SBMT +formatter_settings_version=23 +sp_cleanup.add_all=false +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=true +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.array_with_curly=false +sp_cleanup.arrays_fill=false +sp_cleanup.bitwise_conditional_expression=false +sp_cleanup.boolean_literal=false +sp_cleanup.boolean_value_rather_than_comparison=false +sp_cleanup.break_loop=false +sp_cleanup.collection_cloning=false +sp_cleanup.comparing_on_criteria=false +sp_cleanup.comparison_statement=false +sp_cleanup.controlflow_merge=false +sp_cleanup.convert_functional_interfaces=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.convert_to_enhanced_for_loop_if_loop_var_used=false +sp_cleanup.convert_to_switch_expressions=false +sp_cleanup.correct_indentation=false +sp_cleanup.do_while_rather_than_while=false +sp_cleanup.double_negation=false +sp_cleanup.else_if=false +sp_cleanup.embedded_if=false +sp_cleanup.evaluate_nullable=false +sp_cleanup.extract_increment=false +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.hash=false +sp_cleanup.if_condition=false +sp_cleanup.insert_inferred_type_arguments=false +sp_cleanup.instanceof=false +sp_cleanup.instanceof_keyword=false +sp_cleanup.invert_equals=false +sp_cleanup.join=false +sp_cleanup.lazy_logical_operator=false +sp_cleanup.make_local_variable_final=true +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=false +sp_cleanup.map_cloning=false +sp_cleanup.merge_conditional_blocks=false +sp_cleanup.multi_catch=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.no_string_creation=false +sp_cleanup.no_super=false +sp_cleanup.number_suffix=false +sp_cleanup.objects_equals=false +sp_cleanup.on_save_use_additional_actions=false +sp_cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=false +sp_cleanup.operand_factorization=false +sp_cleanup.organize_imports=true +sp_cleanup.overridden_assignment=false +sp_cleanup.overridden_assignment_move_decl=true +sp_cleanup.plain_replacement=false +sp_cleanup.precompile_regex=false +sp_cleanup.primitive_comparison=false +sp_cleanup.primitive_parsing=false +sp_cleanup.primitive_rather_than_wrapper=false +sp_cleanup.primitive_serialization=false +sp_cleanup.pull_out_if_from_if_else=false +sp_cleanup.pull_up_assignment=false +sp_cleanup.push_down_negation=false +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.reduce_indentation=false +sp_cleanup.redundant_comparator=false +sp_cleanup.redundant_falling_through_block_end=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_redundant_modifiers=false +sp_cleanup.remove_redundant_semicolons=false +sp_cleanup.remove_redundant_type_arguments=false +sp_cleanup.remove_trailing_whitespaces=false +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_array_creation=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_method_parameters=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.return_expression=false +sp_cleanup.simplify_lambda_expression_and_method_ref=false +sp_cleanup.single_used_field=false +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.standard_comparison=false +sp_cleanup.static_inner_class=false +sp_cleanup.strictly_equal_or_different=false +sp_cleanup.stringbuffer_to_stringbuilder=false +sp_cleanup.stringbuilder=false +sp_cleanup.stringbuilder_for_local_vars=true +sp_cleanup.stringconcat_to_textblock=false +sp_cleanup.substring=false +sp_cleanup.switch=false +sp_cleanup.system_property=false +sp_cleanup.system_property_boolean=false +sp_cleanup.system_property_file_encoding=false +sp_cleanup.system_property_file_separator=false +sp_cleanup.system_property_line_separator=false +sp_cleanup.system_property_path_separator=false +sp_cleanup.ternary_operator=false +sp_cleanup.try_with_resource=false +sp_cleanup.unlooped_while=false +sp_cleanup.unreachable_block=false +sp_cleanup.use_anonymous_class_creation=false +sp_cleanup.use_autoboxing=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_directly_map_method=false +sp_cleanup.use_lambda=true +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_string_is_blank=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 +sp_cleanup.use_unboxing=false +sp_cleanup.use_var=false +sp_cleanup.useless_continue=false +sp_cleanup.useless_return=false +sp_cleanup.valueof_rather_than_instantiation=false diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/.settings/org.jboss.ide.eclipse.as.core.prefs b/.settings/org.jboss.ide.eclipse.as.core.prefs new file mode 100644 index 0000000..cf3aa3a --- /dev/null +++ b/.settings/org.jboss.ide.eclipse.as.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.jboss.ide.eclipse.as.core.singledeployable.deployableList= diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1f49660 --- /dev/null +++ b/pom.xml @@ -0,0 +1,222 @@ + + 4.0.0 + edu.jhuapl.ses.jsqrl + jsqrl + jar + 0.0.1 + jsqrl + VTK Support library + + Johns Hopkins University Applied Physics Lab + https://www.jhuapl.edu + + + + central + surfshop-releases + http://surfshop.jhuapl.edu:8081/artifactory/libs-release-local + + + central + surfshop-snapshots + http://surfshop.jhuapl.edu:8081/artifactory/libs-snapshot-local + + + + UTF-8 + UTF-8 + 16 + 16 + 1.0.0.25 + + + + junit + junit + 3.8.1 + test + + + com.google.guava + guava + 31.1-jre + + + com.google.code.gson + gson + 2.8.6 + + + + scm:git:http://hardin:8080/scm/git/vtk/saavtk + scm:git:http://hardin:8080/scm/git/vtk/saavtk + + + + Quiver + https://quiver.jhuapl.edu/saavtk/issues + + + + + + false + + thirdparty + libs-3rdparty-local + http://surfshop.jhuapl.edu:8081/artifactory/libs-3rdparty-local + + + + + false + + central + libs-release + https://surfshop:8081/artifactory/libs-release + + + + snapshots + libs-snapshot + https://surfshop:8081/artifactory/libs-snapshot + + + + false + + OSGeo + release + https://repo.osgeo.org/repository/release/ + + + + false + + central2 + libs-release + https://repo1.maven.org/maven2/ + + + + snapshots2 + libs-snapshot + https://repo1.maven.org/maven2/ + + + + + + false + + central + libs-release + https://surfshop:8081/artifactory/libs-release + + + + snapshots + libs-snapshot + https://surfshop:8081/artifactory/libs-snapshot + + + + false + + central2 + plugins-release + https://repo1.maven.org/maven2/ + + + + snapshots2 + plugins-snapshot + https://repo1.maven.org/maven2/ + + + + + src + + + maven-compiler-plugin + 3.8.1 + + 16 + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + + icons + + compile + + + + + + + + + + run + + + + + + + diff --git a/src/edu/jhuapl/ses/jsqrl/api/Key.java b/src/edu/jhuapl/ses/jsqrl/api/Key.java new file mode 100644 index 0000000..57ffd3e --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/api/Key.java @@ -0,0 +1,84 @@ +package edu.jhuapl.ses.jsqrl.api; + +import java.util.regex.Pattern; + +/** + * @param type of the object that may be associated with this key, used for compile-time safety + * only + */ +public class Key implements Comparable> { + + private static final Pattern NoLeadingWhiteSpace = Pattern.compile("^\\S.*", Pattern.DOTALL); + private static final Pattern NoTrailingWhiteSpace = Pattern.compile(".*\\S$", Pattern.DOTALL); + + /** + * Return a key based on the supplied identification string. + * + * @param keyId the identification string of the key to be returned. + * @return the key + * + * @throws NullPointerException if argument is null + */ + public static Key of(String keyId) { + return new Key<>(keyId); + } + + private final String keyId; + + protected Key(String keyId) { + checkNotNull(keyId); + checkArgument(NoLeadingWhiteSpace.matcher(keyId).matches()); + checkArgument(NoTrailingWhiteSpace.matcher(keyId).matches()); + this.keyId = keyId; + } + + public String getId() { + return keyId; + } + + @Override + public final int compareTo(Key that) { + if (that == null) { + return 1; + } + return this.getId().compareTo(that.getId()); + } + + @Override + public final int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + getId().hashCode(); + return result; + } + + @Override + public final boolean equals(Object other) { + if (this == other) { + return true; + } + if (other instanceof Key) { + Key that = (Key) other; + return this.getId().equals(that.getId()); + } + return false; + } + + @Override + public String toString() { + return keyId; + } + + private static void checkNotNull(Object object) { + if (object == null) { + throw new NullPointerException(); + } + } + + private static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/api/Metadata.java b/src/edu/jhuapl/ses/jsqrl/api/Metadata.java new file mode 100644 index 0000000..f70a4cb --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/api/Metadata.java @@ -0,0 +1,87 @@ +package edu.jhuapl.ses.jsqrl.api; + +import java.util.Collection; + +/** + * Interface representing a heterogenous collection of objects that may be retrieved using keys, + * similar to {@link java.util.Map}, but with significant differences, mainly in how nulls and + * missing keys are treated, and in how type safety is (and is not) enforced. + */ +public interface Metadata { + /** + * Return the {@link Version} for the content of this collection of metadata (not different + * versions of the Metadata interface or associated abstractions). + * + * @return the content version object + */ + Version getVersion(); + + /** + * Get a collection of {@link Key}s contained in this metadata object. Implementors must guarantee + * this method remains consistent with other methods on the class, as described in the get(Key + * key) method. + * + * @return the collection of keys + */ + Collection> getKeys(); + + /** + * Check whether this metadata object has the supplied key. Implementors must guarantee this + * method remains consistent with other methods on the class, as described in the get(Key key) + * method. + * + * @param key the key to check + * @return true if this metadata object has a value associated with the provided key + * @throws NullPointerException if the provided key parameter is null + */ + boolean hasKey(Key key); + + /** + * Retrieve the value associated with the provided key, if that key-value pair is contained in + * this metadata object. Keys may not be null, but implementors are required to support null + * values associated with keys. + *

+ * + * This method provides compile-time type safety only. Because of type erasure, it is possible to + * obtain a key that is present in the metadata, but which has the wrong parametrized type. This + * will lead to a ClassCastException when this method is called. It is the caller's responsibility + * to avoid and/or mitigate this contingency. + *

+ * + * Implementions are required to be self-consistent in the following ways: + *

+ * + * 1) For any metadata object, get(Key key) must throw an IllegalArgumentException if and only + * if hasKey(Key key) would return false for that same key parameter. + *

+ * + * 2) For any metadata object, hasKey(Key key) must return false if the key parameter would not + * be contained in the Collection returned by getKeys(). Implementations may choose whether to + * allow "private keys", i.e., keys not returned by getKeys, but for which hasKey returns true. + *

+ * + * @param key whose associated value to retrieve + * + * @return the value (possibly null) associated with the key + * + * @throws NullPointerException if the provided key parameter is null + * + * @throws IllegalArgumentException if this metadata does not contain a key-value pairing + * associated with the key parameter + * + * @throws ClassCastException if the value associated with the key cannot be cast to the type + * provided by the key's type parameter + */ + V get(Key key); + + /** + * Create a completely independent copy of this metadata object, i.e., a separate object whose + * methods would return identical results to the current object immediately after this method is + * called. Implementations are not required to return an object of the same type as the object on + * which this method is invoked. + * + * @return the copy of the metadata object + */ + Metadata copy(); + +} diff --git a/src/edu/jhuapl/ses/jsqrl/api/MetadataManager.java b/src/edu/jhuapl/ses/jsqrl/api/MetadataManager.java new file mode 100644 index 0000000..6034ecd --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/api/MetadataManager.java @@ -0,0 +1,26 @@ +package edu.jhuapl.ses.jsqrl.api; + +/** + * Abstraction representing a manager of {@link Metadata}, capable of storing/retrieving the content + * or state of one or more objects to/from a single Metadata object. + */ +public interface MetadataManager extends RepresentableAsMetadata { + /** + * Return a (complete and self-consistent) set of metadata derived from the content or state of + * one or more objects. + * + * @return destination object in which the metadata are stored + */ + @Override + Metadata store(); + + /** + * Retrieve a (complete and self-consistent) set of metadata in the provided source object. The + * metadata retrieved will typically be used to create or restore the state of one or more + * objects. + * + * @param source the source metadata object + */ + void retrieve(Metadata source); + +} diff --git a/src/edu/jhuapl/ses/jsqrl/api/ProvidesGenericObjectFromMetadata.java b/src/edu/jhuapl/ses/jsqrl/api/ProvidesGenericObjectFromMetadata.java new file mode 100644 index 0000000..6c88fa4 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/api/ProvidesGenericObjectFromMetadata.java @@ -0,0 +1,28 @@ +package edu.jhuapl.ses.jsqrl.api; + +/** + * Functional interface whose method uses a supplied {@link Metadata} object to provide an object + * instance of a particular parametrized type <T>. If <T> is an instantiable class, this + * implies that the stored metadata must contain all information necessary to instantiate an object + * of type <T>. For non-instantiable types (i.e. enumerations or other singletons), the + * metadata object need only contain enough information to identify the object to return. + *

+ * In general if one provides an implementation of this interface, one provides also a complementary + * implementation of {@link ProvidesMetadataFromGenericObject} interface and registers both of these + * implementations with an {@InstanceGetter}. + * + * @param the object type that can be provided from suitable Metadata + * @see {@link ProvidesMetadataFromGenericObject} + */ +public interface ProvidesGenericObjectFromMetadata { + + /** + * Use the supplied {@link Metadata} to create (or get) an object of the appropriate instance + * type. + * + * @param metadata the metadata to use to provide the instance + * @return an instance of the object of the parametrized type T obtained based on the metadata + */ + T provide(Metadata metadata); + +} diff --git a/src/edu/jhuapl/ses/jsqrl/api/ProvidesMetadataFromGenericObject.java b/src/edu/jhuapl/ses/jsqrl/api/ProvidesMetadataFromGenericObject.java new file mode 100644 index 0000000..7961245 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/api/ProvidesMetadataFromGenericObject.java @@ -0,0 +1,25 @@ +package edu.jhuapl.ses.jsqrl.api; + +/** + * Functional interface whose method uses a supplied object of the parametrized type <T> to + * provide a {@link Metadata} object that encapsulates the identity and/or state of the original + * object. An implementation of this interface is typically paired with an implementation of + * {@link ProvidesGenericObjectFromMetadata}, an implementation of this interface may be used to + * store all information needed to instantiate on object of type <T>. + * + * @param the object type for which Metadata can be provided. + * @see {@link ProvidesGenericObjectFromMetadata} + */ +public interface ProvidesMetadataFromGenericObject { + + /** + * Use the supplied object to create a {@link Metadata} object that encapsulates the state of the + * original object. + * + * @param object the object whose state will be encapsulated in Metadata. + * @return the Metadata. + * @see {@link ProvidesGenericObjectFromMetadata} + */ + Metadata provide(T object); + +} diff --git a/src/edu/jhuapl/ses/jsqrl/api/RepresentableAsMetadata.java b/src/edu/jhuapl/ses/jsqrl/api/RepresentableAsMetadata.java new file mode 100644 index 0000000..5374f9e --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/api/RepresentableAsMetadata.java @@ -0,0 +1,19 @@ +package edu.jhuapl.ses.jsqrl.api; + +/** + * Abstraction that is capable of representing (storing) the content or state of one or more objects + * as {@link Metadata}. + * + * Implementations may provide representations for themselves, or on behalf of other objects. + * + * @see {@link MetadataManager} + */ +public interface RepresentableAsMetadata { + /** + * Return a set of {@link Metadata} that represents the content or state of one or more objects. + * + * @return the metadata representing the object of the parameterized type + */ + Metadata store(); + +} diff --git a/src/edu/jhuapl/ses/jsqrl/api/Serializer.java b/src/edu/jhuapl/ses/jsqrl/api/Serializer.java new file mode 100644 index 0000000..5ae5a05 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/api/Serializer.java @@ -0,0 +1,36 @@ +package edu.jhuapl.ses.jsqrl.api; + +import java.io.File; +import java.io.IOException; + +public interface Serializer { + /** + * Return the version of the serializer/stored file format itself. + * + * @return the version. + */ + Version getVersion(); + + /** + * Register the provided manager to manage Metadata objects associated with the provided key. + * + * Managers are called in the order in which they were originally added. Call this method the very + * first time a method is invoked that uses a Metadata object to save and/or restore the state of + * an object. This may or may not be within a constructor. + * + * This is so that program execution will more-or-less preserve the natural order of operations + * affecting objects whose metadata is serialized/deserialized. + * + * @param key the key identifying the Metadata objects this manager manages + * @param manager the manager for Metadata objects associated with the key + * @throws IllegalStateException if method is called more than once with the same key + */ + void register(Key key, MetadataManager manager); + + void deregister(Key key); + + void load(File file) throws IOException; + + void save(File file) throws IOException; + +} diff --git a/src/edu/jhuapl/ses/jsqrl/api/Spud.java b/src/edu/jhuapl/ses/jsqrl/api/Spud.java new file mode 100644 index 0000000..9cf9918 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/api/Spud.java @@ -0,0 +1,6 @@ +package edu.jhuapl.ses.jsqrl.api; + +public class Spud +{ + +} diff --git a/src/edu/jhuapl/ses/jsqrl/api/StorableAsMetadata.java b/src/edu/jhuapl/ses/jsqrl/api/StorableAsMetadata.java new file mode 100644 index 0000000..08676fa --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/api/StorableAsMetadata.java @@ -0,0 +1,24 @@ +package edu.jhuapl.ses.jsqrl.api; + +/** + * A {@link RepresentableAsMetadata} that is restricted to representing a single object of one + * particular type. This includes a {@link Key} providing a unique identifier of the type of object + * that may be converted to {@link Metadata}. + * + * The existence of the Key identifier makes implementations suitable for serialization of objects + * of the parametrized type. + * + * @param the object type that can be converted to Metadata + * @see {@link ProvidesMetadataFromGenericObject} + */ +public interface StorableAsMetadata extends RepresentableAsMetadata { + /** + * Return a key that uniquely identifies the type of object being stored. This is used in lieu of + * the object's Class to identify the type for purposes of saving (serializing) the + * {@link Metadata}. + * + * @return the key + */ + Key getKey(); + +} diff --git a/src/edu/jhuapl/ses/jsqrl/api/Version.java b/src/edu/jhuapl/ses/jsqrl/api/Version.java new file mode 100644 index 0000000..109bfef --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/api/Version.java @@ -0,0 +1,88 @@ +package edu.jhuapl.ses.jsqrl.api; + +public final class Version implements Comparable { + + // Release notes are now in the package-info file. + + public static Version of(int major, int minor) { + return new Version(major, minor); + } + + public static Version of(String versionString) { + checkNotNull(versionString); + checkArgument(versionString.matches("^\\d+\\.\\d+$")); + String[] versionInfo = versionString.split("\\.", 2); + if (versionInfo.length != 2) { + throw new IllegalArgumentException(); + } + return of(Integer.parseInt(versionInfo[0]), Integer.parseInt(versionInfo[1])); + } + + private final int major; + private final int minor; + + private Version(int major, int minor) { + checkArgument(major >= 0 && minor >= 0); + this.major = major; + this.minor = minor; + } + + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } + + @Override + public int compareTo(Version that) { + int result = 1; + if (that != null) { + result = Integer.compare(this.major, that.major); + if (result == 0) { + result = Integer.compare(this.minor, that.minor); + } + } + return result; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + major; + result = prime * result + minor; + return result; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof Version)) { + return false; + } + Version that = (Version) other; + return (this.major == that.major && this.minor == that.minor); + } + + @Override + public String toString() { + return major + "." + minor; + } + + private static void checkNotNull(Object object) { + if (object == null) { + throw new NullPointerException(); + } + } + + private static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/AbstractMetadata.java b/src/edu/jhuapl/ses/jsqrl/impl/AbstractMetadata.java new file mode 100644 index 0000000..04ef938 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/AbstractMetadata.java @@ -0,0 +1,150 @@ +package edu.jhuapl.ses.jsqrl.impl; + +import java.lang.reflect.Array; +import java.util.Map.Entry; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; + +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.Version; + +/** + * Base implementation that assumes/requires all metadata keys to be stored in a standard + * {@link java.util.Map}. Implementations are provided for all {@link Metadata} methods except copy. + * An additional protected abtract method, getMap() is provided so that subclasses may provide the + * map used by this impementation. To ensure invariants are preserved in sublasses, all methods that + * rely on this implementation's contract are final. + */ +public abstract class AbstractMetadata implements Metadata { + /** + * Object used to represent null. By proxying null with this object, it is possible to use any + * {@link java.util.Map} implementation for key-value pairs. + */ + private static final Object NULL_OBJECT = new Object() { + @Override + public String toString() { + // Deliberately capitalizing this so that the astute debugger has a chance of + // noticing that this object is not actually a null pointer. + return "NULL"; + } + }; + + private final Version version; + + protected AbstractMetadata(Version version) { + Preconditions.checkNotNull(version); + this.version = version; + } + + /** + * Provide an immutable copy of the map of keys to values. Because the returned map does not + * support nulls for keys or values, implementations must use the object returned by getNullObject + * to represent all null values. + * + * @return the map of keys to values + */ + public abstract ImmutableMap, Object> getMap(); + + @Override + public final Version getVersion() { + return version; + } + + @Override + public final boolean hasKey(Key key) { + Preconditions.checkNotNull(key); + return getMap().containsKey(key); + } + + @Override + public final V get(Key key) { + Preconditions.checkNotNull(key); + Object object = getMap().get(key); + if (object == null) { + throw new IllegalArgumentException("FixedMetadata does not contain key " + key); + } + if (getNullObject() == object) { + return null; + } + @SuppressWarnings("unchecked") + V result = (V) object; + return result; + } + + @Override + public abstract AbstractMetadata copy(); + + @Override + public final int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + getVersion().hashCode(); + result = prime * result + getMap().hashCode(); + return result; + } + + @Override + public final boolean equals(Object other) { + if (this == other) { + return true; + } + + if (other instanceof AbstractMetadata) { + AbstractMetadata that = (AbstractMetadata) other; + if (!this.getVersion().equals(that.getVersion())) { + return false; + } + + ImmutableMap, Object> thisMap = this.getMap(); + ImmutableMap, Object> thatMap = that.getMap(); + for (Entry, Object> entry : thisMap.entrySet()) { + Key key = entry.getKey(); + if (!thatMap.containsKey(key)) { + return false; + } + + Object thisValue = entry.getValue(); + Object thatValue = thatMap.get(key); + try { + int thisLength = Array.getLength(thisValue); + int thatLength = Array.getLength(thatValue); + if (thisLength != thatLength) { + return false; + } + for (int index = 0; index < thisLength; ++index) { + if (!Array.get(thisValue, index).equals(Array.get(thatValue, index))) { + return false; + } + } + } catch (@SuppressWarnings("unused") IllegalArgumentException e) { + // One or both items are not arrays, so just fall back on native equals method. + if (!thisValue.equals(thatValue)) { + return false; + } + } + } + + return true; + } + + return false; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("(Metadata) version "); + builder.append(getVersion()); + for (Key key : getKeys()) { + builder.append("\n"); + builder.append(key + " = " + get(key)); + } + return builder.toString(); + } + + protected static Object getNullObject() { + return NULL_OBJECT; + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/AbstractMetadataManager.java b/src/edu/jhuapl/ses/jsqrl/impl/AbstractMetadataManager.java new file mode 100644 index 0000000..edc956e --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/AbstractMetadataManager.java @@ -0,0 +1,23 @@ +package edu.jhuapl.ses.jsqrl.impl; + +import edu.jhuapl.ses.jsqrl.api.MetadataManager; +import edu.jhuapl.ses.jsqrl.api.Version; +import edu.jhuapl.ses.jsqrl.impl.SettableMetadata; + +public abstract class AbstractMetadataManager implements MetadataManager { + + private final Version version; + + protected AbstractMetadataManager(int major, int minor) { + this(Version.of(major, minor)); + } + + protected AbstractMetadataManager(Version version) { + this.version = version; + } + + protected SettableMetadata createMetadata() { + return SettableMetadata.of(version); + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/EmptyMetadata.java b/src/edu/jhuapl/ses/jsqrl/impl/EmptyMetadata.java new file mode 100644 index 0000000..72b3942 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/EmptyMetadata.java @@ -0,0 +1,46 @@ +package edu.jhuapl.ses.jsqrl.impl; + +import java.util.Collection; + +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.Version; + +public final class EmptyMetadata implements Metadata { + private static final EmptyMetadata INSTANCE = new EmptyMetadata(); + + public static EmptyMetadata instance() { + return INSTANCE; + } + + @Override + public Version getVersion() { + throw new UnsupportedOperationException("Empty metadata has no version"); + } + + @Override + public Collection> getKeys() { + throw new UnsupportedOperationException("Empty metadata has no keys"); + } + + @Override + public boolean hasKey(Key key) { + throw new UnsupportedOperationException("Empty metadata does not have key " + key); + } + + @Override + public V get(Key key) { + throw new UnsupportedOperationException("Empty metadata has no value for key " + key); + } + + @Override + public Metadata copy() { + return this; + } + + @Override + public String toString() { + return "Empty metadata"; + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/FixedMetadata.java b/src/edu/jhuapl/ses/jsqrl/impl/FixedMetadata.java new file mode 100644 index 0000000..3beafa1 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/FixedMetadata.java @@ -0,0 +1,59 @@ +package edu.jhuapl.ses.jsqrl.impl; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.Version; +import edu.jhuapl.ses.jsqrl.impl.AbstractMetadata; + +public class FixedMetadata extends AbstractMetadata { + public static FixedMetadata of(Metadata metadata) { + Preconditions.checkNotNull(metadata); + + if (metadata instanceof FixedMetadata) { + return (FixedMetadata) metadata; + } + + Version version = metadata.getVersion(); + ImmutableList> keys = ImmutableList.copyOf(metadata.getKeys()); + + ImmutableMap.Builder, Object> builder = ImmutableMap.builder(); + for (Key key : keys) { + Object object = metadata.get(key); + if (object == null) { + object = getNullObject(); + } + builder.put(key, object); + } + + return new FixedMetadata(version, keys, builder.build()); + } + + private final ImmutableList> keys; + private final ImmutableMap, Object> map; + + protected FixedMetadata(Version version, ImmutableList> keys, + ImmutableMap, Object> map) { + super(version); + this.keys = keys; + this.map = map; + } + + @Override + public ImmutableList> getKeys() { + return keys; + } + + @Override + public FixedMetadata copy() { + return this; + } + + @Override + public ImmutableMap, Object> getMap() { + return map; + } +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/InstanceGetter.java b/src/edu/jhuapl/ses/jsqrl/impl/InstanceGetter.java new file mode 100644 index 0000000..4e6752b --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/InstanceGetter.java @@ -0,0 +1,260 @@ +package edu.jhuapl.ses.jsqrl.impl; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import com.google.common.base.Preconditions; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.ProvidesGenericObjectFromMetadata; +import edu.jhuapl.ses.jsqrl.api.ProvidesMetadataFromGenericObject; +import edu.jhuapl.ses.jsqrl.impl.Utilities; + +/** + * A collection of {@link ProvidesGenericObjectFromMetadata}s that may be stored or retrieved using + * an associated {@link Key}. Keys must be unique within one InstanceGetter. + * + * @param the object type that can be gotten from the Metadata + */ +public final class InstanceGetter { + + /** + * Return a reference to the standard/global InstanceGetter. Most applications will use this so + * that any code may gain access to the various types of available + * {@link ProvidesGenericObjectFromMetadata} objects. + * + * @return the instance + */ + public static InstanceGetter defaultInstanceGetter() { + return DEFAULT_INSTANCE_GETTER; + } + + private static final InstanceGetter DEFAULT_INSTANCE_GETTER = new InstanceGetter(); + + private final SortedMap, ProvidesGenericObjectFromMetadata> fromMetadataMap; + private final Map, ProvidesMetadataFromGenericObject> toMetadataMap; + private final BiMap, Key> keyMap; + private final List> abstractTypes; + private final List> interfaceTypes; + + /** + * Create a new InstanceGetter. In general, it's best to use the standard/global InstanceGetter + * supplied by the defaultInstanceGetter method. However, this constructor is public in case it + * ever becomes necessary to stand up an independent one (say to override how objects of a + * particular class are proxied by default). + */ + public InstanceGetter() { + this.fromMetadataMap = new TreeMap<>(); + this.toMetadataMap = new HashMap<>(); + this.keyMap = HashBiMap.create(); + this.abstractTypes = new ArrayList<>(); + this.interfaceTypes = new ArrayList<>(); + } + + public boolean isProvidableFromMetadata(Key proxyTypeKey) { + Preconditions.checkNotNull(proxyTypeKey); + + return fromMetadataMap.containsKey(proxyTypeKey); + } + + /** + * Get the {@link ProvidesGenericObjectFromMetadata} object that matches the supplied key, + * provided the InstanceGetter has access to one. + * + * @param proxyTypeKey the key uniquely identifying the type of the object to be handled by the + * returned {@link ProvidesGenericObjectFromMetadata} + * @return the helper object that may be used to create/get objects from {@link Metadata} + * @throws IllegalArgumentException if this InstanceGetter does not have a + * {@link ProvidesGenericObjectFromMetadata} object for the supplied key + * @throws ClassCastException if the supplied key matches a + * {@link ProvidesGenericObjectFromMetadata} object managed by this InstanceGetter, but + * the key has the wrong type. + */ + public ProvidesGenericObjectFromMetadata providesGenericObjectFromMetadata( + Key proxyTypeKey) { + Preconditions.checkNotNull(proxyTypeKey); + Preconditions.checkArgument(fromMetadataMap.containsKey(proxyTypeKey), + "Unable to provide proxied object from metadata for type " + proxyTypeKey); + + @SuppressWarnings("unchecked") + ProvidesGenericObjectFromMetadata result = + (ProvidesGenericObjectFromMetadata) fromMetadataMap.get(proxyTypeKey); + + return result; + } + + public boolean isStorableAsMetadata(Object object) { + if (object == null) { + return false; + } + + return isTypeStorableAsMetadata(object.getClass()); + } + + public boolean isTypeStorableAsMetadata(Class type) { + return findBestMatchForType(type) != null; + } + + public Key getKeyForType(Class objectType) { + Preconditions.checkNotNull(objectType); + + Class matchingType = findBestMatchForType(objectType); + + if (matchingType == null) { + throw new IllegalArgumentException(); + } + + @SuppressWarnings("unchecked") + Key result = (Key) keyMap.get(matchingType); + + return result; + } + + public Class getTypeForKey(Key typeKey) { + Preconditions.checkNotNull(typeKey); + BiMap, Class> typeMap = keyMap.inverse(); + Preconditions.checkArgument(typeMap.containsKey(typeKey)); + + @SuppressWarnings("unchecked") + Class result = (Class) typeMap.get(typeKey); + + return result; + } + + public ProvidesMetadataFromGenericObject providesMetadataFromGenericObject( + Class objectType) { + Preconditions.checkNotNull(objectType); + + Class matchingType = findBestMatchForType(objectType); + + Preconditions.checkArgument(matchingType != null); + + @SuppressWarnings("unchecked") + ProvidesMetadataFromGenericObject result = + (ProvidesMetadataFromGenericObject) toMetadataMap.get(matchingType); + + return result; + } + + /** + * Call the 4-argument register method instead of this one. + * + * @param proxyTypeKey + * @param fromMetadata + */ + @Deprecated + public void register(Key proxyTypeKey, + ProvidesGenericObjectFromMetadata fromMetadata) { + Preconditions.checkNotNull(proxyTypeKey); + Preconditions.checkNotNull(fromMetadata); + + Preconditions.checkState(!fromMetadataMap.containsKey(proxyTypeKey), + "Cannot register metadata proxy more than once for type " + proxyTypeKey); + + fromMetadataMap.put(proxyTypeKey, fromMetadata); + } + + /** + * Register proxy metadata store and retrieve objects to work with the supplied key and type. + * + * @param proxyTypeKey the key identifying the type of object that may be obtained by the + * {@link ProvidesGenericObjectFromMetadata}. This is encoded in the stored metadata. + * @param fromMetadata the {@link ProvidesGenericObjectFromMetadata} object to associate with this + * key and value type. This object is used to supply an instance when deserializing. + * @param objectType the specific type of object that may be stored and retrieved to/from + * metadata. + * @param toMetadata the {@link ProvidesMetadataFromGenericObject} object to associate with this + * key and value type. This object is used to encode an instance of the object as metadata. + * @throws IllegalStateException if this InstanceGetter already was called with the supplied + * proxyTypeKey or objectType. + */ + public void register(Key proxyTypeKey, + ProvidesGenericObjectFromMetadata fromMetadata, Class objectType, + ProvidesMetadataFromGenericObject toMetadata) { + Preconditions.checkNotNull(proxyTypeKey); + Preconditions.checkNotNull(fromMetadata); + Preconditions.checkNotNull(objectType); + Preconditions.checkNotNull(toMetadata); + + Preconditions.checkState(!fromMetadataMap.containsKey(proxyTypeKey), + "Cannot register metadata proxy more than once for type " + proxyTypeKey); + Preconditions.checkState(!toMetadataMap.containsKey(objectType), + "Cannot register metadata proxies more than once for object type " + + Utilities.simpleName(objectType)); + Preconditions.checkState(!keyMap.containsKey(objectType), + "Cannot register more than one proxy key for an object type"); + Preconditions.checkState(!keyMap.inverse().containsKey(proxyTypeKey), + "Cannot register more than one object type with the same proxy key"); + + fromMetadataMap.put(proxyTypeKey, fromMetadata); + toMetadataMap.put(objectType, toMetadata); + keyMap.put(objectType, proxyTypeKey); + + if ((objectType.getModifiers() & Modifier.ABSTRACT) != 0) { + abstractTypes.add(objectType); + } + + if (objectType.isInterface()) { + interfaceTypes.add(objectType); + } + + } + + /** + * Deregister (remove/don't track or use) the {@link ProvidesGenericObjectFromMetadata} associated + * with this key, if any. + * + * @param proxyTypeKey the key identifying the MetadataToObject to remove + */ + public void deRegister(Key proxyTypeKey) { + Preconditions.checkNotNull(proxyTypeKey); + + fromMetadataMap.remove(proxyTypeKey); + + Class objectType = keyMap.inverse().get(proxyTypeKey); + if (objectType != null) { + toMetadataMap.remove(objectType); + keyMap.remove(objectType); + abstractTypes.remove(objectType); + interfaceTypes.remove(objectType); + } + } + + protected Class findBestMatchForType(Class objectType) { + Preconditions.checkNotNull(objectType); + + Class result = null; + + if (keyMap.containsKey(objectType)) { + result = objectType; + } + + if (result == null) { + // Search for match to a superclass. + for (Class abstractType : abstractTypes) { + if (abstractType.isAssignableFrom(objectType)) { + result = abstractType; + break; + } + } + } + + if (result == null) { + // Search for first match to an interface. + for (Class interfaceType : interfaceTypes) { + if (interfaceType.isAssignableFrom(objectType)) { + result = interfaceType; + break; + } + } + } + + return result; + } +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/MetadataManagerCollection.java b/src/edu/jhuapl/ses/jsqrl/impl/MetadataManagerCollection.java new file mode 100644 index 0000000..0be6b97 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/MetadataManagerCollection.java @@ -0,0 +1,63 @@ +package edu.jhuapl.ses.jsqrl.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.MetadataManager; + +public class MetadataManagerCollection { + public static MetadataManagerCollection of() { + return new MetadataManagerCollection(); + } + + private final List> keysInOrder; + private final SortedMap, MetadataManager> managers; + + private MetadataManagerCollection() { + this.keysInOrder = new ArrayList<>(); + this.managers = new TreeMap<>(); + } + + /** + * Add a manager to this collection of managers. The manager will be associated with Metadata + * identified by the supplied key. + * + * @param key the key to Metadata objects this manager will manage + * @param manager the manager + * @throws IllegalStateException if there is already a manager associated with the supplied key + * @throws NullPointerException if any argument is null + */ + public void add(Key key, MetadataManager manager) { + Preconditions.checkNotNull(key); + Preconditions.checkNotNull(manager); + Preconditions.checkState(!managers.containsKey(key)); + + keysInOrder.add(key); + managers.put(key, manager); + } + + public void remove(Key key) { + Preconditions.checkNotNull(key); + + keysInOrder.remove(key); + managers.remove(key); + } + + public ImmutableList> getKeys() { + return ImmutableList.copyOf(keysInOrder); + } + + public MetadataManager getManager(Key key) { + Preconditions.checkNotNull(key); + Preconditions.checkArgument(managers.containsKey(key)); + return managers.get(key); + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/SerializableItem.java b/src/edu/jhuapl/ses/jsqrl/impl/SerializableItem.java new file mode 100644 index 0000000..aa647a5 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/SerializableItem.java @@ -0,0 +1,181 @@ +package edu.jhuapl.ses.jsqrl.impl; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Base64; +import java.util.Objects; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import com.google.common.base.Preconditions; +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Version; +import edu.jhuapl.ses.jsqrl.impl.InstanceGetter; +import edu.jhuapl.ses.jsqrl.impl.SettableMetadata; + +/** + * Adaptor to serialize/deserialize objects that do not explicitly cooperate with the metadata + * framework, but that *do* implement {@link Serializable}. This adaptor uses byte array objects and + * gzipping/gunzipping streams to serialize an object to a string, then saves that string using the + * metadata framework. This adaptor can also do the reverse and create an object from a string that + * holds the object's serialized form. + *

+ * The purpose of this class is to make it easier to support hierarchies and complex objects with + * the metadata framework without writing explicit instance-getting code. The trade-off is that the + * strings written are long and not human readable, whereas writing instance-getting code for each + * type allows thec caller to control how objects are written and read. + * + * @author James Peachey + * + * @param the type of item, which must extend {@link Serializable} + */ +public final class SerializableItem { + + /** + * Boilerplate metadata stuff. + */ + private static final Version MetadataVersion = Version.of(1, 0); + private static final Key> ItemTypeKey = Key.of("itemType"); + private static final Key EncodedStringKey = Key.of("encodedString"); + + static { + + /** + * Add to the metadata framework the necessary code to handle encoding/decoding the item. + */ + InstanceGetter.defaultInstanceGetter().register(Key.of("SerializableItem"), // + metadata -> { + Class itemType = metadata.get(ItemTypeKey); + String encodedString = metadata.get(EncodedStringKey); + + Serializable serializable; + try { + serializable = decodeFromString(itemType, encodedString); + } catch (Exception e) { + serializable = null; + } + + return new SerializableItem(itemType, serializable); + }, SerializableItem.class, settings -> { + SettableMetadata metadata = SettableMetadata.of(MetadataVersion); + + metadata.put(ItemTypeKey, settings.getItemType()); + String encodedString; + try { + encodedString = encodeToString(settings.getItem()); + } catch (Exception e) { + encodedString = null; + } + metadata.put(EncodedStringKey, encodedString); + + return metadata; + }); + + } + + /** + * Empty static method that may be called to ensure static initialization blocks have executed. + */ + public static void init() { + + } + + /** + * Decode an item that is encoded in the specified string and cast it to the specified item type. + * + * @param generic type, which must extend {@link Serializable} + * @param itemType type of item being decoded + * @param encodedString the string + * @return the decoded item + * @throws IOException if the gunzipping/streaming operation(s) throw one + * @throws ClassNotFoundException if the class of the object type being decoded cannot be found + */ + public static S decodeFromString(Class itemType, String encodedString) + throws IOException, ClassNotFoundException { + Preconditions.checkNotNull(itemType); + + if (encodedString == null) { + return null; + } + + byte[] data = Base64.getDecoder().decode(encodedString); + + try (ObjectInputStream ois = + new ObjectInputStream(new GZIPInputStream(new ByteArrayInputStream(data)))) { + return itemType.cast(ois.readObject()); + } + } + + /** + * Encode the specified {@link Serializable} item into a string. + * + * @param item the item to encode + * @return the encoded string + * @throws IOException if the gzipping/streaming operation(s) throw one + */ + public static String encodeToString(Serializable item) throws IOException { + if (item == null || item instanceof String) { + return (String) item; + } + + String encodedString = null; + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + try (ObjectOutputStream oos = + new ObjectOutputStream(/* new GZIPOutputStream( */new GZIPOutputStream(baos))) { + oos.writeObject(item); + } + encodedString = Base64.getEncoder().encodeToString(baos.toByteArray()); + } + + return encodedString; + } + + private final Class itemType; + private final S item; + + public SerializableItem(Class itemType, S item) { + this.itemType = itemType; + this.item = item; + } + + /** + * @return the concrete type of the serializable item + */ + public Class getItemType() { + return itemType; + } + + /** + * @return the item that can be serialized + */ + public S getItem() { + return item; + } + + @Override + public int hashCode() { + return Objects.hash(item, itemType); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof SerializableItem)) { + return false; + } + SerializableItem other = (SerializableItem) obj; + + return Objects.equals(item, other.item) && Objects.equals(itemType, other.itemType); + } + + @Override + public String toString() { + return item != null ? item.toString() : null; + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/SettableMetadata.java b/src/edu/jhuapl/ses/jsqrl/impl/SettableMetadata.java new file mode 100644 index 0000000..093e1c1 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/SettableMetadata.java @@ -0,0 +1,178 @@ +package edu.jhuapl.ses.jsqrl.impl; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +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 java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.Version; +import edu.jhuapl.ses.jsqrl.impl.AbstractMetadata; +import edu.jhuapl.ses.jsqrl.impl.Utilities; + +public class SettableMetadata extends AbstractMetadata { + + public static SettableMetadata of(Version version) { + return new SettableMetadata(version, new ArrayList<>(), new HashMap<>()); + } + + public static SettableMetadata of(Metadata metadata) { + Preconditions.checkNotNull(metadata); + SettableMetadata result = SettableMetadata.of(metadata.getVersion()); + for (Key key : metadata.getKeys()) { + @SuppressWarnings("unchecked") + Key objectKey = (Key) key; + result.put(objectKey, metadata.get(key)); + } + return result; + } + + private final List> keys; + private final Map, Object> map; + + protected SettableMetadata(Version version, List> keys, Map, Object> map) { + super(version); + Preconditions.checkNotNull(keys); + Preconditions.checkNotNull(map); + this.keys = keys; + this.map = map; + } + + @Override + public ImmutableList> getKeys() { + return ImmutableList.copyOf(keys); + } + + @Override + public SettableMetadata copy() { + return new SettableMetadata(getVersion(), new ArrayList<>(keys), new HashMap<>(map)); + } + + @Override + public ImmutableMap, Object> getMap() { + return ImmutableMap.copyOf(map); + } + + public final SettableMetadata put(Key key, V value) { + Preconditions.checkNotNull(key); + Class storedAsType = checkStorable(value); + if (!hasKey(key)) { + keys.add(key); + } + if (value == null) { + map.put(key, getNullObject()); + } else { + map.put(key, copyOrUse(storedAsType, value)); + } + return this; + } + + public void clear() { + keys.clear(); + map.clear(); + } + + protected static void validateIterable(Iterable iterable) { + for (Object item : iterable) { + checkStorable(item); + } + } + + protected static void validateMap(Map map) { + Class mapKeyType = null; + for (Entry entry : map.entrySet()) { + Class keyType = checkStorable(entry.getKey()); + if (mapKeyType == null) { + mapKeyType = keyType; + } else if (keyType != null && keyType != mapKeyType) { + throw new IllegalArgumentException( + "Cannot put a key of type " + Utilities.simpleName(keyType) + + " in a map using keys of type " + Utilities.simpleName(mapKeyType)); + } + checkStorable(entry.getValue()); + } + } + + @SuppressWarnings("unchecked") + protected static V copyOrUse(Class storedAsType, V value) { + if (SortedMap.class.isAssignableFrom(storedAsType)) { + value = (V) new TreeMap<>((SortedMap) value); + } else if (Map.class.isAssignableFrom(storedAsType)) { + value = (V) new LinkedHashMap<>((Map) value); + } else if (List.class.isAssignableFrom(storedAsType)) { + value = (V) new ArrayList<>((List) value); + } else if (SortedSet.class.isAssignableFrom(storedAsType)) { + value = (V) new TreeSet<>((SortedSet) value); + } else if (Set.class.isAssignableFrom(storedAsType)) { + value = (V) new LinkedHashSet<>((Set) value); + } + return value; + } + + /** + * Check whether the specified object may be represented as metadata. If it can, the type used by + * the metadata system to represent the object is returned. If the object cannot be represented by + * the metadata system, an {@link IllegalArgumentException} is thrown. This method never returns + * null. + *

+ * This method uses the {@link Utilities#classifyStorableType(Class)} to determine whether the + * object is storable using the primary metadata mechanism. Unlike that method, however, if the + * object is not storable in the primary way, but the item's class implements + * {@link Serializable}, this method returns Serializable.class, because the item will be + * successfully stored/retrieved using the secondary mechanism for storing Serializable items, in + * which objects are serialized as Strings. + * + * @param object the item to be stored as metadata + * @return the type used to represent this item + */ + protected static Class checkStorable(Object object) { + if (object == null) { + return null; + } + if (object instanceof List) { + validateIterable((Iterable) object); + return List.class; + } + if (object instanceof SortedMap) { + validateMap((Map) object); + return SortedMap.class; + } + if (object instanceof Map) { + validateMap((Map) object); + return Map.class; + } + if (object instanceof SortedSet) { + validateIterable((Iterable) object); + return SortedSet.class; + } + if (object instanceof Set) { + validateIterable((Iterable) object); + return Set.class; + } + + Class type = Utilities.classifyStorableType(object.getClass()); + + if (type == null && Serializable.class.isAssignableFrom(object.getClass())) { + type = Serializable.class; + } + + if (type == null) { + throw new IllegalArgumentException("Cannot directly represent objects of type " + + Utilities.simpleName(object.getClass()) + " as metadata"); + } + + return type; + } +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/TrackedMetadataManager.java b/src/edu/jhuapl/ses/jsqrl/impl/TrackedMetadataManager.java new file mode 100644 index 0000000..32e14fc --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/TrackedMetadataManager.java @@ -0,0 +1,99 @@ +package edu.jhuapl.ses.jsqrl.impl; + +import java.util.SortedSet; +import java.util.TreeSet; + +import com.google.common.base.Preconditions; + +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.MetadataManager; +import edu.jhuapl.ses.jsqrl.api.RepresentableAsMetadata; +import edu.jhuapl.ses.jsqrl.api.Serializer; +import edu.jhuapl.ses.jsqrl.impl.gson.Serializers; + +/** + * This is a helper implementation designed to wrap another implementation of the + * {@link MetadataManager} interface. + */ +public final class TrackedMetadataManager implements MetadataManager, RepresentableAsMetadata { + public static TrackedMetadataManager of(String metadataId) { + return new TrackedMetadataManager(metadataId, Serializers.getDefault()); + } + + private static final SortedSet MANAGER_IDENTIFIERS = new TreeSet<>(); + private final Key metadataKey; + private final Serializer serializer; + private MetadataManager manager; + + /** + * Construct a new manager that will use the provided serializer. The identifying string provided + * must be unique within the currently running application. Note that the new manager is not added + * to the serializer by this constructor. That will be done when another manager is registered + * with this manager. + * + * @param metadataId string used to idenfity this manager within the serializer + * @param serializer the serializer + * @throws IllegalStateException if the serializer already has a manager associated with the + * provided string + * @throws NullPointerException if any argument is null + */ + private TrackedMetadataManager(String metadataId, Serializer serializer) { + Preconditions.checkNotNull(metadataId); + Preconditions.checkNotNull(serializer); + Preconditions.checkState(!MANAGER_IDENTIFIERS.contains(metadataId), + "Duplicated manager identifier " + metadataId); + MANAGER_IDENTIFIERS.add(metadataId); + this.metadataKey = Key.of(metadataId); + this.serializer = serializer; + this.manager = null; + } + + /** + * Perform all initializations required prior to serializing/deserializing metadata managed by + * this manager. Note that all of this implementation's functions as a manager work just fine + * without calling this method. Moreover, it is safe to call this method multiple times. + * + * A separate initialize method is provided so that source objects may be serialized in the + * natural order established by the runtime function of the tool, rather than the order in which + * source objects were instantiated, which may not be the same. + * + * @throws IllegalStateException if a manager was already registered, or if the serializer fails + * to register this manager cleanly. + */ + public void register(MetadataManager manager) { + Preconditions.checkNotNull(manager); + Preconditions.checkState(this.manager == null); + serializer.register(metadataKey, this); + this.manager = manager; + } + + public boolean isRegistered() { + return manager != null; + } + + public Key getKey() { + return metadataKey; + } + + /** + * {@inheritDoc} + */ + @Override + public final Metadata store() { + Preconditions.checkState(manager != null); + return manager.store(); + } + + /** + * {@inheritDoc} + */ + @Override + public final void retrieve(Metadata sourceMetadata) { + Preconditions.checkNotNull(sourceMetadata); + Preconditions.checkState(manager != null); + + manager.retrieve(sourceMetadata); + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/Utilities.java b/src/edu/jhuapl/ses/jsqrl/impl/Utilities.java new file mode 100644 index 0000000..ed465ef --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/Utilities.java @@ -0,0 +1,232 @@ +package edu.jhuapl.ses.jsqrl.impl; + +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.MetadataManager; +import edu.jhuapl.ses.jsqrl.api.ProvidesMetadataFromGenericObject; +import edu.jhuapl.ses.jsqrl.api.StorableAsMetadata; +import edu.jhuapl.ses.jsqrl.api.Version; +import edu.jhuapl.ses.jsqrl.impl.InstanceGetter; +import edu.jhuapl.ses.jsqrl.impl.SettableMetadata; + +public class Utilities { + + // Release notes are now in the package-info file. + + public static ImmutableMap bulkStore(Map managers) { + Preconditions.checkNotNull(managers); + + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Entry entry : managers.entrySet()) { + builder.put(entry.getKey(), entry.getValue().store()); + } + + return builder.build(); + } + + public static void bulkRetrieve(Map managers, Map metadata) { + Preconditions.checkNotNull(managers); + Preconditions.checkNotNull(metadata); + for (Entry entry : managers.entrySet()) { + K key = entry.getKey(); + if (metadata.containsKey(key)) { + entry.getValue().retrieve(metadata.get(key)); + } + } + } + + public static Key provideTypeKeyIfPossible(Object object) { + InstanceGetter instanceGetter = InstanceGetter.defaultInstanceGetter(); + + Key key = null; + if (instanceGetter.isStorableAsMetadata(object)) { + key = instanceGetter.getKeyForType(object.getClass()); + } + + return key; + } + + public static Object provideMetadataIfPossible(Object object) { + InstanceGetter instanceGetter = InstanceGetter.defaultInstanceGetter(); + if (instanceGetter.isStorableAsMetadata(object)) { + + @SuppressWarnings("unchecked") + Class objectType = (Class) object.getClass(); + + object = instanceGetter.providesMetadataFromGenericObject(objectType).provide(object); + } + + return object; + } + + public static String simpleName(Class type) { + if (type == null) { + return null; + } + + String simpleName = type.getSimpleName(); + if (simpleName.isEmpty()) { + simpleName = type.getName().replaceAll(".*//.", ""); + } + + return simpleName; + } + + /** + * Determine how to represent the specified type as metadata. For all the types the metadata + * system was designed to handle natively (int, Map, etc.) and for types handled by the + * {@link InstanceGetter} mechanism, the appropriate base class or interface used to set up + * serialization/deserialization is returned. If the type is not one of these directly supported + * types, null is returned. + *

+ * The interface {@link java.io.Serializable} is a special case. If the specified type is not one + * of the directly-supported types described above, but the type is assignable to Serializable, + * this method returns null. This signifies that the type in question is not supported by the + * primary metadata mechanism. However, while this method returns null, the method + * {@link SettableMetadata#checkStorable(Object)} returns Serializable.class in this case. This + * works because there is a secondary mechanism for Serializable items, in which they are + * serialized into a String. This is for dealing with hierarchies for which no explicit metadata + * rules are set up for whatever reason. + * + * @param type the actual type of object to be represented as metadata + * @return the type the metadata system will use to store/retrieve such objects + */ + public static Class classifyStorableType(Class type) { + if (type == null) { + return null; + } + if (InstanceGetter.defaultInstanceGetter().isTypeStorableAsMetadata(type)) { + return ProvidesMetadataFromGenericObject.class; + } + if (Key.class.isAssignableFrom(type)) { + return Key.class; + } + if (Metadata.class.isAssignableFrom(type)) { + return Metadata.class; + } + if (StorableAsMetadata.class.isAssignableFrom(type)) { + return StorableAsMetadata.class; + } + if (Version.class.isAssignableFrom(type)) { + return Version.class; + } + if (List.class.isAssignableFrom(type)) { + return List.class; + } + if (SortedMap.class.isAssignableFrom(type)) { + return SortedMap.class; + } + if (Map.class.isAssignableFrom(type)) { + return Map.class; + } + if (SortedSet.class.isAssignableFrom(type)) { + return SortedSet.class; + } + if (Set.class.isAssignableFrom(type)) { + return Set.class; + } + if (String.class.isAssignableFrom(type)) { + return String.class; + } + if (Character.class.isAssignableFrom(type)) { + return Character.class; + } + if (Boolean.class.isAssignableFrom(type)) { + return Boolean.class; + } + if (Double.class.isAssignableFrom(type)) { + return Double.class; + } + if (Float.class.isAssignableFrom(type)) { + return Float.class; + } + if (Integer.class.isAssignableFrom(type)) { + return Integer.class; + } + if (Long.class.isAssignableFrom(type)) { + return Long.class; + } + if (Short.class.isAssignableFrom(type)) { + return Short.class; + } + if (Byte.class.isAssignableFrom(type)) { + return Byte.class; + } + if (Date.class.isAssignableFrom(type)) { + return Date.class; + } + if (String[].class.isAssignableFrom(type)) { + return String[].class; + } + if (Character[].class.isAssignableFrom(type)) { + return Character[].class; + } + if (Boolean[].class.isAssignableFrom(type)) { + return Boolean[].class; + } + if (Double[].class.isAssignableFrom(type)) { + return Double[].class; + } + if (Float[].class.isAssignableFrom(type)) { + return Float[].class; + } + if (Integer[].class.isAssignableFrom(type)) { + return Integer[].class; + } + if (Long[].class.isAssignableFrom(type)) { + return Long[].class; + } + if (Short[].class.isAssignableFrom(type)) { + return Short[].class; + } + if (Byte[].class.isAssignableFrom(type)) { + return Byte[].class; + } + if (Date[].class.isAssignableFrom(type)) { + return Date[].class; + } + if (Metadata[].class.isAssignableFrom(type)) { + return Metadata[].class; + } + if (char[].class.isAssignableFrom(type)) { + return char[].class; + } + if (boolean[].class.isAssignableFrom(type)) { + return boolean[].class; + } + if (double[].class.isAssignableFrom(type)) { + return double[].class; + } + if (float[].class.isAssignableFrom(type)) { + return float[].class; + } + if (int[].class.isAssignableFrom(type)) { + return int[].class; + } + if (long[].class.isAssignableFrom(type)) { + return long[].class; + } + if (short[].class.isAssignableFrom(type)) { + return short[].class; + } + if (byte[].class.isAssignableFrom(type)) { + return byte[].class; + } + if (Class.class.isAssignableFrom(type)) { + return Class.class; + } + + return null; + } + + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/ClassIO.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/ClassIO.java new file mode 100644 index 0000000..8e317aa --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/ClassIO.java @@ -0,0 +1,77 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.impl.InstanceGetter; + +public class ClassIO implements JsonSerializer>, JsonDeserializer> { + + private static final InstanceGetter instanceGetter = InstanceGetter.defaultInstanceGetter(); + + @Override + public JsonElement serialize(Class src, Type typeOfSrc, JsonSerializationContext context) { + String typeId = null; + + try { + Key key = instanceGetter.getKeyForType(src); + typeId = key.getId(); + } catch (@SuppressWarnings("unused") Exception e) { + + } + + if (typeId == null) { + DataTypeInfo info = DataTypeInfo.of(src); + if (info != DataTypeInfo.NULL) { + typeId = info.getTypeId(); + } + } + + if (typeId == null) { + typeId = src.getName(); + } + + return new JsonPrimitive(typeId); + } + + @Override + public Class deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + String typeId = json.getAsJsonPrimitive().getAsString(); + Class result = null; + + try { + result = instanceGetter.getTypeForKey(Key.of(typeId)); + } catch (@SuppressWarnings("unused") Exception e) { + + } + + if (result == null) { + try { + DataTypeInfo info = DataTypeInfo.of(typeId); + result = info.getTypeClass(); + } catch (@SuppressWarnings("unused") Exception e) { + + } + } + + if (result == null) { + try { + result = Class.forName(typeId); + } catch (@SuppressWarnings("unused") Exception e) { + result = Object.class; + } + } + + return result; + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/DataTypeInfo.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/DataTypeInfo.java new file mode 100644 index 0000000..9082532 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/DataTypeInfo.java @@ -0,0 +1,178 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.io.Serializable; +import java.lang.reflect.Type; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import com.google.gson.reflect.TypeToken; +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.StorableAsMetadata; +import edu.jhuapl.ses.jsqrl.api.Version; +import edu.jhuapl.ses.jsqrl.impl.InstanceGetter; +import edu.jhuapl.ses.jsqrl.impl.Utilities; + +/** + * Enumerations representing different forms of type information associated with particular classes. + * These enumerations are used via reflection to serialize objects, so it is important not to remove + * any of these enumerations, or the associated types of objects will not be serializable. This is + * true even if the particular enumerated items are not explicitly referenced. + * + * This enumeration must be kept consistent with the object types supported by the metadata package. + */ +enum DataTypeInfo { + //////////////////////////////////////////////////////////////// + // Metadata-specific types. + METADATA_KEY("Key", Key.class, new TypeToken>() {}.getType()), // + METADATA("Metadata", Metadata.class, new TypeToken() {}.getType()), // + VERSION("Version", Version.class, new TypeToken() {}.getType()), // + ELEMENT("Element", GsonElement.class, new TypeToken() {}.getType()), // + PROXIED_OBJECT("ProxiedObject", StorableAsMetadata.class, + new TypeToken>() {}.getType()), + //////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////// + // Collection types. + LIST("List", List.class, new TypeToken>() {}.getType()), // + SORTED_MAP("SortedMap", SortedMap.class, new TypeToken>() {}.getType()), // + MAP("Map", Map.class, new TypeToken>() {}.getType()), // + SORTED_SET("SortedSet", SortedSet.class, new TypeToken>() {}.getType()), // + SET("Set", Set.class, new TypeToken>() {}.getType()), // + ITERABLE("Iterable", Iterable.class, new TypeToken>() {}.getType()), // + //////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////// + // Common object types. + STRING("String", String.class, new TypeToken() {}.getType()), // + CHARACTER_OBJECT("Character", Character.class, new TypeToken() {}.getType()), // + BOOLEAN_OBJECT("Boolean", Boolean.class, new TypeToken() {}.getType()), // + // Floating-point types. + DOUBLE_OBJECT("Double", Double.class, new TypeToken() {}.getType()), // + FLOAT_OBJECT("Float", Float.class, new TypeToken() {}.getType()), // + // Integer types. + INTEGER_OBJECT("Integer", Integer.class, new TypeToken() {}.getType()), // + LONG_OBJECT("Long", Long.class, new TypeToken() {}.getType()), // + SHORT_OBJECT("Short", Short.class, new TypeToken() {}.getType()), // + BYTE_OBJECT("Byte", Byte.class, new TypeToken() {}.getType()), // + DATE_OBJECT("Date", Date.class, new TypeToken() {}.getType()), // + //////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////// + // Arrays of common object types. + STRING_ARRAY("String Array", String[].class, new TypeToken() {}.getType()), // + CHARACTER_OBJECT_ARRAY("Character Array", Character[].class, + new TypeToken() {}.getType()), // + BOOLEAN_OBJECT_ARRAY("Boolean Array", Boolean[].class, new TypeToken() {}.getType()), // + // Floating-point types. + DOUBLE_OBJECT_ARRAY("Double Array", Double[].class, new TypeToken() {}.getType()), // + FLOAT_OBJECT_ARRAY("Float Array", Float[].class, new TypeToken() {}.getType()), // + // Integer types. + INTEGER_OBJECT_ARRAY("Integer Array", Integer[].class, new TypeToken() {}.getType()), // + LONG_OBJECT_ARRAY("Long Array", Long[].class, new TypeToken() {}.getType()), // + SHORT_OBJECT_ARRAY("Short Array", Short[].class, new TypeToken() {}.getType()), // + BYTE_OBJECT_ARRAY("Byte Array", Byte[].class, new TypeToken() {}.getType()), // + DATE_ARRAY("Date Array", Date[].class, new TypeToken() {}.getType()), // + METADATA_ARRAY("metadata Array", Metadata[].class, new TypeToken() {}.getType()), // + //////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////// + // Arrays of primitive types. + CHARACTER_ARRAY("char Array", char[].class, new TypeToken() {}.getType()), // + BOOLEAN_ARRAY("boolean Array", boolean[].class, new TypeToken() {}.getType()), // + // Floating-point types. + DOUBLE_ARRAY("double Array", double[].class, new TypeToken() {}.getType()), // + FLOAT_ARRAY("float Array", float[].class, new TypeToken() {}.getType()), // + // Integer types. + INTEGER_ARRAY("int Array", int[].class, new TypeToken() {}.getType()), // + LONG_ARRAY("long Array", long[].class, new TypeToken() {}.getType()), // + SHORT_ARRAY("short Array", short[].class, new TypeToken() {}.getType()), // + BYTE_ARRAY("byte Array", byte[].class, new TypeToken() {}.getType()), // + //////////////////////////////////////////////////////////////// + + // In a class by itself: Class: + CLASS("Class", Class.class, new TypeToken>() {}.getType()), // + SERIALIZABLE("Serializable", Serializable.class, new TypeToken() {}.getType()), // + + // Catch-all case used both to handle nulls and to detect objects that cannot be serialized. + NULL("Null", Object.class, new TypeToken() {}.getType()), // + ; + + public static DataTypeInfo of(String typeId) { + for (DataTypeInfo info : values()) { + if (info.typeId.equals(typeId)) { + return info; + } + } + throw new IllegalArgumentException("Cannot decode unknown type " + typeId); + } + + public static DataTypeInfo of(Class valueClass) { + for (DataTypeInfo info : values()) { + if (info.valueClass.isAssignableFrom(valueClass)) { + return info; + } + } + + // The final type info matches Object, which should be assignable from anything. + throw new AssertionError(); + } + + public static DataTypeInfo of(Type type) { + for (DataTypeInfo info : values()) { + if (info.type.equals(type)) { + return info; + } + } + throw new IllegalArgumentException(); + } + + public static DataTypeInfo forObject(Object object) { + DataTypeInfo result = NULL; + if (object != null) { + if (InstanceGetter.defaultInstanceGetter().isStorableAsMetadata(object)) { + result = METADATA; + } + if (result == NULL) { + result = of(object.getClass()); + } + if (result == NULL) { + throw new IllegalArgumentException("Cannot serialize object of type " + + Utilities.simpleName(object.getClass()) + " to JSON format"); + } + } + return result; + } + + private final String typeId; + private final Class valueClass; + private final Type type; + + private DataTypeInfo(String typeId, Class valueClass, Type type) { + this.typeId = typeId; + this.valueClass = valueClass; + this.type = type; + } + + public String getTypeId() { + return typeId; + } + + public Class getTypeClass() { + return valueClass; + } + + public Type getType() { + return type; + } + + @Override + public String toString() { + return "TypeInfo: " + typeId + ", Type = " + type + ", class = " + + Utilities.simpleName(valueClass); + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/GsonElement.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/GsonElement.java new file mode 100644 index 0000000..2ece1e6 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/GsonElement.java @@ -0,0 +1,201 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; +import java.util.Map.Entry; +import java.util.Set; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.Version; +import edu.jhuapl.ses.jsqrl.impl.InstanceGetter; +import edu.jhuapl.ses.jsqrl.impl.Utilities; + +final class GsonElement { + + static final class ElementIO + implements JsonSerializer, JsonDeserializer { + @Override + public GsonElement deserialize(JsonElement jsonElement, Type typeOfT, + JsonDeserializationContext context) throws JsonParseException { + Preconditions.checkArgument(DataTypeInfo.ELEMENT.getType().equals(typeOfT)); + return GsonElement.of(jsonElement, context); + } + + @Override + public JsonElement serialize(GsonElement src, Type typeOfSrc, + JsonSerializationContext context) { + Preconditions.checkArgument(DataTypeInfo.ELEMENT.getType().equals(typeOfSrc)); + return src.toElement(context); + } + + } + + private static final InstanceGetter INSTANCE_GETTER = InstanceGetter.defaultInstanceGetter(); + + static final String VALUE_TYPE_KEY = "valueType"; + + public static JsonElement encodeItem(Object item, boolean excludeMetadataVersion, + JsonSerializationContext context) { + + item = Utilities.provideMetadataIfPossible(item); + + DataTypeInfo typeInfo = DataTypeInfo.forObject(item); + + if (excludeMetadataVersion && typeInfo == DataTypeInfo.METADATA) { + return MetadataIOv2.encodeWithoutVersion((Metadata) item, context); + } + + return context.serialize(item, typeInfo.getType()); + } + + public static JsonElement encodeItemWithType(Object item, boolean excludeMetadataVersion, + JsonSerializationContext context) { + + Key typeKey = Utilities.provideTypeKeyIfPossible(item); + + item = Utilities.provideMetadataIfPossible(item); + + String typeId = typeKey != null ? typeKey.getId() : DataTypeInfo.forObject(item).getTypeId(); + + JsonObject result = new JsonObject(); + + result.add(typeId, encodeItem(item, excludeMetadataVersion, context)); + + return result; + } + + public static void encodeTypeInfo(String typeId, JsonObject encodedItem) { + encodedItem.addProperty(VALUE_TYPE_KEY, typeId); + } + + /** + * Decode item according to the supplied type, using the supplied version (if not null) for all + * Metadata. + * + * @param encodedItem + * @param typeInfo + * @param context + * @return + */ + public static Object decodeItem(JsonElement encodedItem, Key typeKey, Version commonVersion, + JsonDeserializationContext context) { + + if (INSTANCE_GETTER.isProvidableFromMetadata(typeKey)) { + Metadata metadata = + (Metadata) decodeItem(encodedItem, DataTypeInfo.METADATA, commonVersion, context); + return INSTANCE_GETTER.providesGenericObjectFromMetadata(typeKey).provide(metadata); + } + + DataTypeInfo typeInfo = DataTypeInfo.of(typeKey.getId()); + return decodeItem(encodedItem, typeInfo, commonVersion, context); + } + + private static Object decodeItem(JsonElement encodedItem, DataTypeInfo typeInfo, + Version commonVersion, JsonDeserializationContext context) { + + if (commonVersion != null && typeInfo == DataTypeInfo.METADATA) { + return MetadataIOv2.decode(encodedItem.getAsJsonObject(), commonVersion, context); + } + + return context.deserialize(encodedItem, typeInfo.getType()); + } + + /** + * Decode item using self-contained type informtion but with the supplied Version for decoding + * compressed Metadata. + * + * @param encodedItem + * @param commonVersion + * @param context + * @return + */ + public static Object decodeItem(JsonElement encodedItem, Version commonVersion, + JsonDeserializationContext context) { + Preconditions.checkArgument(encodedItem.isJsonObject()); + + JsonObject encodedTypeAndValue = encodedItem.getAsJsonObject(); + Preconditions.checkState(encodedTypeAndValue.size() == 1); + + // Easiest way to get to the single entry is just to use a one-time loop. + // That way we don't have to mess with the entry set, iterators etc. + for (Entry entry : encodedTypeAndValue.entrySet()) { + Key proxyTypeKey = Key.of(entry.getKey()); + JsonElement encodedValue = entry.getValue(); + + if (INSTANCE_GETTER.isProvidableFromMetadata(proxyTypeKey)) { + Metadata metadata = + (Metadata) decodeItem(encodedValue, DataTypeInfo.METADATA, commonVersion, context); + return INSTANCE_GETTER.providesGenericObjectFromMetadata(proxyTypeKey).provide(metadata); + } + + DataTypeInfo typeInfo = DataTypeInfo.of(proxyTypeKey.getId()); + + return decodeItem(encodedValue, typeInfo, commonVersion, context); + } + + // Can't get here because the loop above is guaranteed to execute at least once. + throw new AssertionError(); + } + + public static Key decodeTypeInfo(JsonObject encodedItem) { + Key typeKey = null; + + if (encodedItem.has(VALUE_TYPE_KEY)) { + JsonElement encodedType = encodedItem.get(VALUE_TYPE_KEY); + typeKey = Key.of(encodedType.getAsString()); + } + + return typeKey; + } + + public static GsonElement of(Object object) { + return new GsonElement(object, DataTypeInfo.forObject(object)); + } + + private static GsonElement of(JsonElement element, JsonDeserializationContext context) { + Preconditions.checkNotNull(element); + Preconditions.checkNotNull(context); + Preconditions.checkArgument(element.isJsonObject()); + + JsonObject valueObject = element.getAsJsonObject(); + + Set> entryList = valueObject.entrySet(); + Preconditions.checkState(entryList.size() == 1); + + Entry entry = entryList.iterator().next(); + DataTypeInfo objectInfo = DataTypeInfo.of(entry.getKey()); + + Object object = context.deserialize(entry.getValue(), objectInfo.getType()); + + return new GsonElement(object, objectInfo); + } + + private final Object object; + private final DataTypeInfo objectInfo; + + private GsonElement(Object object, DataTypeInfo objectInfo) { + Preconditions.checkNotNull(objectInfo); + this.object = object; + this.objectInfo = objectInfo; + } + + public JsonElement toElement(JsonSerializationContext context) { + JsonObject result = new JsonObject(); + result.add(objectInfo.getTypeId(), context.serialize(object, objectInfo.getType())); + return result; + } + + public Object getValue() { + return object; + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/GsonSerializer.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/GsonSerializer.java new file mode 100644 index 0000000..faa1f92 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/GsonSerializer.java @@ -0,0 +1,788 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedSet; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.MetadataManager; +import edu.jhuapl.ses.jsqrl.api.RepresentableAsMetadata; +import edu.jhuapl.ses.jsqrl.api.Serializer; +import edu.jhuapl.ses.jsqrl.api.StorableAsMetadata; +import edu.jhuapl.ses.jsqrl.api.Version; +import edu.jhuapl.ses.jsqrl.impl.InstanceGetter; +import edu.jhuapl.ses.jsqrl.impl.SettableMetadata; +import edu.jhuapl.ses.jsqrl.impl.EmptyMetadata; +import edu.jhuapl.ses.jsqrl.impl.MetadataManagerCollection; +import edu.jhuapl.ses.jsqrl.impl.gson.GsonElement.ElementIO; + +public class GsonSerializer implements Serializer { + + // Release notes are now in the package-info file. + + // Encapsulation versions. + // This version supports serializing proxy class metadata using the same format used + // by standard Metadata (as in the Metadata interface). Instead of the key "Metadata", + // the name of the type serves as the key in the output file format. Previous versions used a + // separate proxy encapsulation. For backward compatibility, the proxy is still supported. + private static final Version SERIALIZER_VERSION_4 = Version.of(4, 0); + + // This version reads and writes syntactically correct JSON format files. Previous versions + // were flawed in that they stored two consecutive JSON objects at the top level of the file. + private static final Version SERIALIZER_VERSION_3 = Version.of(3, 0); + + // This version added support for: + // 1) saving/loading heterogeneous collections and + // 2) compressed format when a collection of Metadata objects happen to have the same + // version. In this case the version is stored once for the whole array or collection. + private static final Version SERIALIZER_VERSION_2 = Version.of(2, 0); + + // Initial version. + private static final Version SERIALIZER_VERSION_1 = Version.of(1, 0); + + private final MetadataManagerCollection managerCollection; + + public static GsonSerializer of() { + return new GsonSerializer(); + } + + protected GsonSerializer() { + this.managerCollection = MetadataManagerCollection.of(); + } + + @Override + public Version getVersion() { + return SERIALIZER_VERSION_4; + } + + @Override + public void register(Key key, MetadataManager manager) { + managerCollection.add(key, manager); + } + + @Override + public void deregister(Key key) { + managerCollection.remove(key); + } + + @Override + public void load(File file) throws IOException { + Preconditions.checkNotNull(file); + + Version fileVersion = loadVersion(file); + + if (SERIALIZER_VERSION_3.compareTo(fileVersion) > 0) { + loadBeforeV3(file); + return; + } + Gson gson = configureGson(fileVersion); + + try (JsonReader reader = gson.newJsonReader(new FileReader(file))) { + Metadata source = gson.fromJson(reader, DataTypeInfo.METADATA.getType()); + retrieveInSingleThreadContext(source); + } + + } + + @Override + public void save(File file) throws IOException { + Preconditions.checkNotNull(file); + + File dir = file.getParentFile(); + if (!dir.exists()) { + dir.mkdirs(); + } + save(file, getVersion()); + } + + private Version loadVersion(File file) throws IOException { + Preconditions.checkNotNull(file); + + Gson gson = createGsonBuilder().create(); + try (JsonReader jsonReader = gson.newJsonReader(new FileReader(file))) { + jsonReader.beginObject(); + + String name = jsonReader.nextName(); + if (!name.equals(DataTypeInfo.VERSION.getTypeId())) { + throw new IOException("Invalid Metadata file format"); + } + + String versionString = jsonReader.nextString(); + + return Version.of(versionString); + } + + } + + private void loadBeforeV3(File file) throws IOException { + Preconditions.checkNotNull(file); + + SettableMetadata source = SettableMetadata.of(Version.of(0, 0)); + Gson versionOnlyGson = configureGsonToReadVersionOnly(); + try (JsonReader reader = versionOnlyGson.newJsonReader(new FileReader(file))) { + Version fileVersion = null; + try { + fileVersion = versionOnlyGson.fromJson(reader, DataTypeInfo.VERSION.getType()); + } catch (Exception e) { + throw new IOException("Metadata reader version " + getVersion() + + " cannot read metadata format of file " + file, e); + } + + Gson gson = configureGson(fileVersion); + + Map metadataMap = gson.fromJson(reader, DataTypeInfo.MAP.getType()); + for (Entry entry : metadataMap.entrySet()) { + source.put(Key.of(entry.getKey()), entry.getValue()); + } + } + retrieveInSingleThreadContext(source); + } + + private void save(File file, Version version) throws IOException { + if (version.equals(SERIALIZER_VERSION_1)) { + saveV1(file); + return; + } else if (version.equals(SERIALIZER_VERSION_2)) { + saveV2(file); + return; + } + + Gson gson = configureGson(version); + try (FileWriter fileWriter = new FileWriter(file)) { + try (JsonWriter jsonWriter = gson.newJsonWriter(fileWriter)) { + + SettableMetadata metaMetadata = SettableMetadata.of(version); + Map metadataMap = new LinkedHashMap<>(); + for (Key key : managerCollection.getKeys()) { + MetadataManager manager = managerCollection.getManager(key); + Metadata metadata = manager.store(); + if (!EmptyMetadata.instance().equals(metadata)) { + metaMetadata.put(Key.of(key.getId()), metadata); + metadataMap.put(key.getId(), metadata); + } + } + + gson.toJson(metaMetadata, DataTypeInfo.METADATA.getType(), jsonWriter); + + fileWriter.write('\n'); + } + } + } + + private void saveV2(File file) throws IOException { + Preconditions.checkNotNull(file); + Gson gson = configureGson(SERIALIZER_VERSION_2); + try (FileWriter fileWriter = new FileWriter(file)) { + try (JsonWriter jsonWriter = gson.newJsonWriter(fileWriter)) { + Map metadataMap = new HashMap<>(); + for (Key key : managerCollection.getKeys()) { + MetadataManager manager = managerCollection.getManager(key); + Metadata metadata = manager.store(); + if (!EmptyMetadata.instance().equals(metadata)) { + metadataMap.put(key.getId(), metadata); + } + } + gson.toJson(SERIALIZER_VERSION_2, DataTypeInfo.VERSION.getType(), jsonWriter); + jsonWriter.flush(); + fileWriter.write('\n'); + gson.toJson(metadataMap, DataTypeInfo.MAP.getType(), jsonWriter); + jsonWriter.flush(); + fileWriter.write('\n'); + } + } + } + + private void saveV1(File file) throws IOException { + Preconditions.checkNotNull(file); + + Gson gson = configureGson(SERIALIZER_VERSION_1); + try (FileWriter fileWriter = new FileWriter(file)) { + try (JsonWriter jsonWriter = gson.newJsonWriter(fileWriter)) { + Map metadataMap = new HashMap<>(); + for (Key key : managerCollection.getKeys()) { + MetadataManager manager = managerCollection.getManager(key); + Metadata metadata = manager.store(); + if (!EmptyMetadata.instance().equals(metadata)) { + metadataMap.put(key.getId(), metadata); + } + } + gson.toJson(SERIALIZER_VERSION_1, DataTypeInfo.VERSION.getType(), jsonWriter); + jsonWriter.flush(); + fileWriter.write('\n'); + gson.toJson(metadataMap, DataTypeInfo.MAP.getType(), jsonWriter); + jsonWriter.flush(); + fileWriter.write('\n'); + } + } + } + + private void retrieveInSingleThreadContext(Metadata source) { + for (Key key : managerCollection.getKeys()) { + if (source.hasKey(key)) { + Metadata element = source.get(key); + if (element != null) { + managerCollection.getManager(key).retrieve(element); + } + } + } + } + + private static Gson configureGsonToReadVersionOnly() { + GsonBuilder builder = new GsonBuilder(); + builder.registerTypeAdapter(DataTypeInfo.VERSION.getType(), new GsonVersionIO()); + return builder.create(); + } + + private static Gson configureGson(Version serializerVersion) { + if (SERIALIZER_VERSION_1.equals(serializerVersion)) { + return configureGsonV1(); + } + + return configureGsonBuilder().create(); + } + + private static GsonBuilder createGsonBuilder() { + GsonBuilder builder = new GsonBuilder(); + + builder.serializeNulls(); + builder.setPrettyPrinting(); + builder.serializeSpecialFloatingPointValues(); + builder.disableHtmlEscaping(); + + return builder; + } + + private static GsonBuilder configureGsonBuilder() { + GsonBuilder builder = createGsonBuilder(); + + builder.registerTypeAdapter(DataTypeInfo.SORTED_SET.getType(), new SortedSetIOv2()); + builder.registerTypeAdapter(DataTypeInfo.SET.getType(), new SetIOv2()); + builder.registerTypeAdapter(DataTypeInfo.LIST.getType(), new ListIOv2()); + builder.registerTypeAdapter(DataTypeInfo.ITERABLE.getType(), new ListIOv2()); + builder.registerTypeAdapter(DataTypeInfo.SORTED_MAP.getType(), new SortedMapIOv2()); + builder.registerTypeAdapter(DataTypeInfo.MAP.getType(), new MapIOv2()); + builder.registerTypeAdapter(DataTypeInfo.METADATA.getType(), new MetadataIOv2()); + builder.registerTypeAdapter(DataTypeInfo.VERSION.getType(), new GsonVersionIO()); + builder.registerTypeAdapter(DataTypeInfo.ELEMENT.getType(), new ElementIO()); + builder.registerTypeAdapter(DataTypeInfo.PROXIED_OBJECT.getType(), new ProxyIOv2<>()); + builder.registerTypeAdapter(DataTypeInfo.CLASS.getType(), new ClassIO()); + builder.registerTypeAdapter(DataTypeInfo.SERIALIZABLE.getType(), new SerializableIO()); + + return builder; + } + + private static Gson configureGsonV1() { + GsonBuilder builder = createGsonBuilder(); + + builder.registerTypeAdapter(DataTypeInfo.SORTED_SET.getType(), new SortedSetIOv1()); + builder.registerTypeAdapter(DataTypeInfo.SET.getType(), new SetIOv1()); + builder.registerTypeAdapter(DataTypeInfo.LIST.getType(), new ListIOv1()); + builder.registerTypeAdapter(DataTypeInfo.ITERABLE.getType(), new ListIOv1()); + builder.registerTypeAdapter(DataTypeInfo.SORTED_MAP.getType(), new SortedMapIOv1()); + builder.registerTypeAdapter(DataTypeInfo.MAP.getType(), new MapIOv1()); + builder.registerTypeAdapter(DataTypeInfo.METADATA.getType(), new MetadataIOv1()); + builder.registerTypeAdapter(DataTypeInfo.VERSION.getType(), new GsonVersionIO()); + builder.registerTypeAdapter(DataTypeInfo.ELEMENT.getType(), new ElementIO()); + builder.registerTypeAdapter(DataTypeInfo.PROXIED_OBJECT.getType(), new ProxyIOv1<>()); + builder.registerTypeAdapter(DataTypeInfo.CLASS.getType(), new ClassIO()); + + return builder.create(); + } + + /** + * This enumeration demonstrates how to serialize/deserialize an enumeration by implementing the + * StorableAsMetadata interface. + * + */ + private static final Key NAME = Key.of("Name"); + private static final Key TEST_ENUM_PROXY_KEY = Key.of("TestEnum"); + private static final Version VERSION = Version.of(1, 0); + + private enum TestEnum implements StorableAsMetadata { + OPTION0("Option 0"), OPTION1("Option 1"); + + // Need to live with this warning until the register method has been phased out. + public static void register(InstanceGetter instanceGetter) { + instanceGetter.register(TEST_ENUM_PROXY_KEY, (metadata) -> { + return valueOf(metadata.get(NAME)); + }); + } + + private final String text; + + TestEnum(String text) { + this.text = text; + } + + @Override + public String toString() { + return text; + } + + @Override + public Key getKey() { + return TEST_ENUM_PROXY_KEY; + } + + @Override + public Metadata store() { + return SettableMetadata.of(VERSION).put(NAME, name()); + } + } + + /** + * This enumeration demonstrates how to serialize/deserialize an enumeration without implementing + * StorableAsMetadata. + */ + private enum TestEnum2 { + OPTION0, OPTION1; + } + + private static final Key TEST_ENUM2_PROXY_KEY = Key.of("TestEnum2"); + + private static void registerTestEnum2(InstanceGetter instanceGetter) { + instanceGetter.register(TEST_ENUM2_PROXY_KEY, metadata -> { + return TestEnum2.valueOf(metadata.get(NAME)); + }, TestEnum2.class, object -> { + return SettableMetadata.of(VERSION).put(NAME, object.name()); + }); + } + + private static class TestManager implements MetadataManager { + private final SettableMetadata metadata; + + TestManager(SettableMetadata metadata) { + this.metadata = metadata; + } + + @Override + public Metadata store() { + SettableMetadata destination = SettableMetadata.of(metadata.getVersion()); + for (Key key : metadata.getKeys()) { + @SuppressWarnings("unchecked") + Key newKey = (Key) key; + destination.put(newKey, metadata.get(key)); + } + + return destination; + } + + @Override + public void retrieve(Metadata source) { + metadata.clear(); + for (Key key : source.getKeys()) { + Object value = source.get(key); + @SuppressWarnings("unchecked") + Key newKey = (Key) key; + metadata.put(newKey, value); + } + } + + } + + // These are all just fodder for test cases, These version numbers have nothing to do with the + // version of + // the serializer. + private static final Version SAMPLE_METADATA_VERSION = Version.of(1, 0); + private static final Version SAMPLE_SUB_METADATA_VERSION = Version.of(3, 1); + private static final String SUB_METADATA_ID = "Bennu / V3"; + private static final Key SAMPLE_METADATA_KEY = Key.of("testState"); + private static final Key SAMPLE_SUB_METADATA_KEY = Key.of(SUB_METADATA_ID); + private static final Key>> LIST_LIST_STRING_KEY = Key.of("listListString"); + + public static void main(String[] args) throws IOException { + TestEnum.register(InstanceGetter.defaultInstanceGetter()); + registerTestEnum2(InstanceGetter.defaultInstanceGetter()); + + // Test failure when trying to serialize something that can't be serialized. + { + class UnserializableObject { + + }; + + UnserializableObject unserializableObject = new UnserializableObject(); + + try { + SettableMetadata.of(SAMPLE_METADATA_VERSION).put(Key.of("unserializable"), + unserializableObject); + System.err.println("Saving an unserializable object did not throw an exception"); + } catch (@SuppressWarnings("unused") IllegalArgumentException e) { + // This is as it should be. + } + } + + // Test V1. + String testPath = Paths.get(System.getProperty("user.home"), "Downloads").toString(); + { + SettableMetadata state = createV1SampleMetadata(); + File file = Paths.get(testPath, "MyStateV1.sbmt").toFile(); + testSaveAndReloadState(state, file, SERIALIZER_VERSION_1); + } + + // Test V2. + { + SettableMetadata state = createV2SampleMetadata(); + File file = Paths.get(testPath, "MyStateV2.sbmt").toFile(); + testSaveAndReloadState(state, file, SERIALIZER_VERSION_2); + } + + // Test V3. + { + SettableMetadata state = createV3SampleMetadata(); + File file = Paths.get(testPath, "MyStateV3.sbmt").toFile(); + testSaveAndReloadState(state, file, SERIALIZER_VERSION_3); + } + + // Test current version. + { + SettableMetadata state = createV4SampleMetadata(); + File file = Paths.get(testPath, "MyState.sbmt").toFile(); + state = testSaveAndReloadState(state, file, SERIALIZER_VERSION_4); + + SettableMetadata subState = state.get(SAMPLE_SUB_METADATA_KEY); + + // Test some further unpacking. + state.get(Key.of("double array")); + state.get(Key.of("int array")); + state.get(Key.of("Double array")); + state.get(Key.of("Integer array")); + state.get(Key.of("String array")); + + subState.get(Key.of("longNull")); + if (TestEnum.OPTION1 != subState.get(Key.of("testEnum"))) { + System.err.println("testEnum value was not option 1"); + } + + state.get(Key.of("stringSet")); + + if (TestEnum2.OPTION1 != subState.get(Key.of("testEnum2"))) { + System.err + .println("testEnum2 value was " + subState.get(Key.of("testEnum2")) + ", not option 1"); + } + + // This fails at runtime. If this were a real key templated on Long it would fail at compile + // time. + // Float fVal = subState.get(Key.of("long")); + + // This doesn't fail at runtime or compile time but I wish it would: + // List> unpackedListList = state.get(Key.of("listListString")); + + // But the following does fail to compile, which is probably good enough. + // unpackedListList = state.get(listListStringKey); + // And this fails at runtime, as it should. + // System.out.println(unpackedListList.get(1).get(1) * 7); + + // It would be OK if this were to work but it doesn't. + // float fVal = subState.get(Key.of("resolution")); + + // This one is supposed to throw an exception. + // Short longAsShort = subState.get(Key.of("facets")); + // System.err.println("long retrieved as a short is " + longAsShort); + } + + } + + private static SettableMetadata createV1SampleMetadata() { + + SettableMetadata subState = SettableMetadata.of(SAMPLE_SUB_METADATA_VERSION); + TestEnum testEnumBefore = TestEnum.OPTION1; + + subState.put(Key.of("tab"), "1"); + subState.put(Key.of("facets"), 2000000001L); + subState.put(Key.of("showBaseMap"), true); + subState.put(Key.of("resolution"), -5.e64); + subState.put(Key.of("int"), 20); + subState.put(Key.of("long"), (long) 20); + subState.put(Key.of("short"), (short) 20); + subState.put(Key.of("byte"), (byte) 20); + subState.put(Key.of("double"), (double) 20); + subState.put(Key.of("float"), (float) 20); + subState.put(Key.of("char"), (char) 20); + subState.put(Key.of("boolean"), false); + subState.put(Key.of("string"), "a string"); + subState.put(Key.of("htmlString"), "
A literal escaped in html
"); + subState.put(Key.of("stringNull"), null); + subState.put(Key.of("longNull"), null); + subState.put(Key.of("testEnum"), testEnumBefore); + subState.put(Key.of("List"), ImmutableList.of(TestEnum.OPTION1, TestEnum.OPTION0)); + + List stringList = new ArrayList<>(); + stringList.add("String0"); + stringList.add(null); + stringList.add("String2"); + Key> stringListKey = Key.of("stringList"); + subState.put(stringListKey, stringList); + + List intList = new ArrayList<>(); + intList.add(0); + intList.add(null); + intList.add(2); + Key> intListKey = Key.of("intList"); + subState.put(intListKey, intList); + + List> listListString = new ArrayList<>(); + listListString.add(null); + listListString.add(ImmutableList.of("X", "y", "z")); + listListString.add(stringList); + + final SettableMetadata state = SettableMetadata.of(SAMPLE_METADATA_VERSION); + + state.put(Key.of("Current View"), SUB_METADATA_ID); + state.put(Key.of("Tab Number"), new Integer(3)); + state.put(Key.of("Current View2"), SUB_METADATA_ID); + state.put(LIST_LIST_STRING_KEY, listListString); + state.put(Key.of("stringSet"), ImmutableSortedSet.of("liver", "spleen", "aardvark")); + state.put(Key.of("Metadata"), "a string that happens to have the key \"Metadata\""); + // The following type has built-in support through DataTypeInfo. + state.put(Key.of("Double.class"), Double.class); + // The following type is registered with InstanceGetter. This should work using the registerd + // name. + state.put(Key.of("TestEnum2.class"), TestEnum2.class); + // The following type is not registered with InstanceGetter. This should also work using the + // full class name. + state.put(Key.of("Metadata.class"), RepresentableAsMetadata.class); + + Map byteShortMap = new HashMap<>(); + byteShortMap.put((byte) 1, null); + byteShortMap.put(null, (short) 12); + byteShortMap.put((byte) 11, (short) 23); + byteShortMap.put((byte) 10, (short) 17); + Key> byteShortMapKey = Key.of("byteShortMap"); + state.put(byteShortMapKey, byteShortMap); + + SortedMap byteSortedMap = new TreeMap<>(); + byteSortedMap.put((byte) 1, null); + byteSortedMap.put((byte) 11, (short) 23); + byteSortedMap.put((byte) 10, (short) 17); + Key> byteSortedMapKey = Key.of("byteSortedMap"); + state.put(byteSortedMapKey, byteSortedMap); + + SortedMap> homogeneousMapMap = new TreeMap<>(); + homogeneousMapMap.put("nullMap", null); + SortedMap intMap = new TreeMap<>(); + intMap.put(0, "zero"); + intMap.put(1, "one"); + homogeneousMapMap.put("map0", intMap); + homogeneousMapMap.put("map1", intMap); + state.put(Key.of("homogeneous map of map"), homogeneousMapMap); + homogeneousMapMap = state.get(Key.of("homogeneous map of map")); + + SortedSet treeSet = new TreeSet<>(); + treeSet.add("string0"); + state.put(Key.of("treeSet"), treeSet); + treeSet = state.get(Key.of("treeSet")); + + state.put(Key.of("single element map"), ImmutableMap.of("key0", "value0")); + state.put(Key.of("single element list"), ImmutableList.of(7)); + + state.put(Key.of("double array"), new double[] {3., 4., 5.}); + state.put(Key.of("int array"), new int[] {6, 7, 8}); + state.put(Key.of("Double array"), new Double[] {9., 10., 11.}); + state.put(Key.of("Integer array"), new Integer[] {12, 13, 14}); + state.put(Key.of("String array"), new String[] {"a", "b", "c"}); + + state.put(SAMPLE_SUB_METADATA_KEY, subState); + + return state; + } + + private static SettableMetadata createV2SampleMetadata() { + // Start with the V1 metadata set. + SettableMetadata state = createV1SampleMetadata(); + SettableMetadata subState = state.get(SAMPLE_SUB_METADATA_KEY); + + // Add tests for things supported in V2 and above. + SortedMap> mapHeterogeneousMap = new TreeMap<>(); + mapHeterogeneousMap.put("nullMap", null); + SortedMap nestedMap = new TreeMap<>(); + nestedMap.put(0, "zero"); + nestedMap.put(1, 1.); + mapHeterogeneousMap.put("map0", nestedMap); + mapHeterogeneousMap.put("map1", nestedMap); + state.put(Key.of("map of heterogenous maps"), mapHeterogeneousMap); + + List intList = subState.get(Key.of("intList")); + + SortedMap mapOfCollections = new TreeMap<>(); + mapOfCollections.put("nullMap", null); + mapOfCollections.put("map", nestedMap); + mapOfCollections.put("intList", intList); + state.put(Key.of("map containing nested objects"), mapOfCollections); + + List objectList = new ArrayList(); + objectList.add(null); + objectList.add("a"); + objectList.add(1); + objectList.add(2.); + objectList.add(false); + objectList.add(SettableMetadata.of(Version.of(1, 3))); + objectList.add(SettableMetadata.of(Version.of(1, 3)).put(Key.of("key0"), "value0")); + subState.put(Key.of("objectList"), objectList); + + Set objectSet = new HashSet<>(); + objectSet.add("a"); + objectSet.add(null); + objectSet.add(1); + objectSet.add(2.); + objectSet.add(true); + objectSet.add(SettableMetadata.of(Version.of(1, 3)).put(Key.of("key0"), "value0")); + subState.put(Key.of("objectSet"), objectSet); + + Map objectMap = new HashMap<>(); + objectMap.put("key0", "repeatedValue"); + objectMap.put("key1", "repeatedValue"); + objectMap.put(null, "valueForNullKey"); + objectMap.put("key2", "distinctValue"); + subState.put(Key.of("homogeneousObjectMap"), objectMap); + try { + // Now test exception for heterogenous keys. + objectMap.put(7, "stringValue"); + subState.put(Key.of("invalidObjectMap"), objectMap); + } catch (@SuppressWarnings("unused") IllegalArgumentException e) { + // This is as it should be. + } + + objectMap = new HashMap<>(); + objectMap.put("key0", "repeatedValue"); + objectMap.put("key1", "repeatedValue"); + objectMap.put(null, 0); + objectMap.put("key2", 4.2); + objectMap.put("meta0", SettableMetadata.of(Version.of(2, 2)).put(Key.of("key0"), "value0")); + objectMap.put("meta1", SettableMetadata.of(Version.of(2, 2)).put(Key.of("key1"), "value1")); + objectMap.put("meta2", SettableMetadata.of(Version.of(2, 3)).put(Key.of("key1"), "value1")); + subState.put(Key.of("heterogeneousObjectMap"), objectMap); + + objectMap = new HashMap<>(); + objectMap.put("key0", "repeatedValue"); + objectMap.put("key1", "repeatedValue"); + objectMap.put(null, 0); + objectMap.put("key2", 4.2); + objectMap.put("meta0", SettableMetadata.of(Version.of(2, 2)).put(Key.of("key0"), "value0")); + objectMap.put("meta1", SettableMetadata.of(Version.of(2, 2)).put(Key.of("key0"), "value0") + .put(Key.of("key1"), "value1")); + subState.put(Key.of("heterogeneousObjectHomogeneousMetadataMap"), objectMap); + + objectMap = new HashMap<>(); + objectMap.put("meta0", SettableMetadata.of(Version.of(2, 2)).put(Key.of("key0"), "value0")); + objectMap.put("meta1", SettableMetadata.of(Version.of(2, 2)).put(Key.of("key1"), "value1")); + subState.put(Key.of("homogeneousMetadataMap"), objectMap); + + objectMap = new HashMap<>(); + objectMap.put("meta0", SettableMetadata.of(Version.of(2, 2)).put(Key.of("key0"), "value0")); + objectMap.put("meta1", SettableMetadata.of(Version.of(2, 3)).put(Key.of("key1"), "value1")); + subState.put(Key.of("heterogeneousMetadataMap"), objectMap); + + List metadataList = new ArrayList<>(); + metadataList.add(SettableMetadata.of(Version.of(0, 1)).put(Key.of("key0"), "version 0.1")); + metadataList.add(SettableMetadata.of(Version.of(0, 1)).put(Key.of("key0"), "version 0.1") + .put(Key.of("key1"), "version 0.1")); + subState.put(Key.of("metadataList"), metadataList); + metadataList.add(SettableMetadata.of(Version.of(0, 2)).put(Key.of("key2"), "version 0.2")); + subState.put(Key.of("heterogeneousMetadataList"), metadataList); + + Set metadataSet = new HashSet<>(); + metadataSet.add(SettableMetadata.of(Version.of(0, 1)).put(Key.of("key0"), "version 0.1")); + metadataSet.add(SettableMetadata.of(Version.of(0, 1)).put(Key.of("key0"), "version 0.1") + .put(Key.of("key1"), "version 0.1")); + subState.put(Key.of("metadataSet"), metadataSet); + metadataSet.add(SettableMetadata.of(Version.of(0, 2)).put(Key.of("key2"), "version 0.2")); + subState.put(Key.of("heterogeneousMetadataSet"), metadataSet); + + SettableMetadata homogeneousMetaMetadata = SettableMetadata.of(Version.of(2, 0)); + homogeneousMetaMetadata.put(Key.of("md0"), + SettableMetadata.of(Version.of(2, 0)).put(Key.of("key0"), "value0")); + homogeneousMetaMetadata.put(Key.of("md1"), + SettableMetadata.of(Version.of(2, 0)).put(Key.of("key0"), 100).put(Key.of("key1"), 101.)); + // subState.put(Key.of("homogeneousMetaMetadata"), homogeneousMetaMetadata); + + return state; + } + + private static SettableMetadata createV3SampleMetadata() { + // V3 was a change to the low level format; no difference in the types of objects that could be + // supported. + return createV2SampleMetadata(); + } + + private static SettableMetadata createV4SampleMetadata() { + SettableMetadata state = createV3SampleMetadata(); + + SettableMetadata subState = state.get(SAMPLE_SUB_METADATA_KEY); + + // Starting in V4, possible to serialize proxy objects directly as metadata using newer + // capabilities of the InstanceGetter class. + TestEnum2 testEnum2Before = TestEnum2.OPTION1; + subState.put(Key.of("testEnum2"), testEnum2Before); + subState.put(Key.of("testEnum2List"), ImmutableList.of(TestEnum2.OPTION1, TestEnum2.OPTION0)); + subState.put(Key.of("testEnum2Map"), + ImmutableMap.of("enumA", TestEnum2.OPTION1, "enumB", TestEnum2.OPTION0)); + + return state; + } + + private static SettableMetadata testSaveAndReloadState(SettableMetadata originalState, File file, + Version saveVersion) throws IOException { + SettableMetadata originalSubState = originalState.get(SAMPLE_SUB_METADATA_KEY); + + { + TestManager stateManager = new TestManager(originalState); + TestManager subStateManager = new TestManager(originalSubState); + + GsonSerializer serializer = GsonSerializer.of(); + serializer.register(SAMPLE_METADATA_KEY, stateManager); + serializer.register(SAMPLE_SUB_METADATA_KEY, subStateManager); + + serializer.save(file, saveVersion); + } + + { + // Blank states with the same keys as the originals. + SettableMetadata reloadedState = SettableMetadata.of(SAMPLE_METADATA_VERSION); + SettableMetadata reloadedSubState = SettableMetadata.of(SAMPLE_SUB_METADATA_VERSION); + + TestManager stateManager = new TestManager(reloadedState); + TestManager subStateManager = new TestManager(reloadedSubState); + + GsonSerializer serializer = GsonSerializer.of(); + // Go in reverse order here just to test that the order doesn't need to be the same. + serializer.register(SAMPLE_SUB_METADATA_KEY, subStateManager); + serializer.register(SAMPLE_METADATA_KEY, stateManager); + + serializer.load(file); + + System.out.println("Reloaded " + saveVersion + " state was" + + (originalState.equals(reloadedState) ? " " : " ******* NOT ******* ") + + "found equal to original"); + + System.out.println("Reloaded " + saveVersion + " sampleState was" + + (originalSubState.equals(reloadedSubState) ? " " : " ******* NOT ******* ") + + "found equal to original"); + + return reloadedState; + } + + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/GsonVersionIO.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/GsonVersionIO.java new file mode 100644 index 0000000..6ad827b --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/GsonVersionIO.java @@ -0,0 +1,65 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import edu.jhuapl.ses.jsqrl.api.Version; + +final class GsonVersionIO implements JsonSerializer, JsonDeserializer { + private static final String KEY_ID = DataTypeInfo.VERSION.getTypeId(); + + public static JsonPrimitive encode(Version version) { + Preconditions.checkNotNull(version); + return new JsonPrimitive(version.toString()); + } + + public static Version decode(JsonPrimitive jsonVersion) { + Preconditions.checkNotNull(jsonVersion); + + return Version.of(jsonVersion.getAsString()); + } + + public static Version decode(JsonObject jsonObject) { + Preconditions.checkNotNull(jsonObject); + + JsonElement jsonVersion = jsonObject.get(KEY_ID); + Preconditions.checkArgument(jsonVersion.isJsonPrimitive()); + + return decode(jsonVersion.getAsJsonPrimitive()); + } + + @Override + public JsonElement serialize(Version src, @SuppressWarnings("unused") Type typeOfSrc, + @SuppressWarnings("unused") JsonSerializationContext context) { + JsonObject result = new JsonObject(); + result.add(KEY_ID, encode(src)); + return result; + } + + @Override + public Version deserialize(JsonElement jsonElement, @SuppressWarnings("unused") Type typeOfT, + @SuppressWarnings("unused") JsonDeserializationContext context) { + Preconditions.checkNotNull(jsonElement); + Preconditions.checkArgument(jsonElement.isJsonObject()); + + JsonObject object = jsonElement.getAsJsonObject(); + + // Unpack metadata. + JsonElement keyIdElement = object.get(KEY_ID); + if (keyIdElement == null || !keyIdElement.isJsonPrimitive()) { + throw new IllegalArgumentException( + "Field \"" + KEY_ID + "\" is missing or has wrong type in Json object"); + } + + return decode(keyIdElement.getAsJsonPrimitive()); + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/IterableIOv1.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/IterableIOv1.java new file mode 100644 index 0000000..51b583e --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/IterableIOv1.java @@ -0,0 +1,144 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +abstract class IterableIOv1 implements JsonSerializer> { + /** + * Internal utility class; just here so the unpack method can return all the distinct pieces of + * information contained in the input JsonElement. + */ + protected static class DeserializedJsonArray { + protected final DataTypeInfo dataTypeInfo; + protected final JsonArray jsonArray; + + protected DeserializedJsonArray(DataTypeInfo dataTypeInfo, JsonArray jsonArray) { + this.dataTypeInfo = dataTypeInfo; + this.jsonArray = jsonArray; + } + } + + protected IterableIOv1() { + + } + + private static final String ITERABLE_VALUE_TYPE = "valueType"; + private static final String ITERABLE_VALUE = "value"; + + @Override + public JsonElement serialize(Iterable src, @SuppressWarnings("unused") Type typeOfSrc, + JsonSerializationContext context) { + JsonObject result = new JsonObject(); + + // First pass: if any entries are found, determine types of key and value based on the type of + // the first non-null element. + DataTypeInfo valueInfo = DataTypeInfo.NULL; + for (Object value : src) { + if (valueInfo == DataTypeInfo.NULL) { + valueInfo = DataTypeInfo.forObject(value); + } + if (valueInfo != DataTypeInfo.NULL) { + break; + } + } + + // Second pass: write the collection's entries to a JsonArray. + JsonArray destArray = new JsonArray(); + Type valueType = valueInfo.getType(); + + for (Object value : src) { + destArray.add(context.serialize(value, valueType)); + } + + // Put iterable metadata and data into the resultant object. + result.add(ITERABLE_VALUE_TYPE, context.serialize(valueInfo.getTypeId())); + result.add(ITERABLE_VALUE, destArray); + + return result; + } + + protected DeserializedJsonArray unpack(JsonElement jsonElement) { + if (!jsonElement.isJsonObject()) { + throw new IllegalArgumentException(); + } + + JsonObject object = jsonElement.getAsJsonObject(); + + // Unpack metadata. + JsonElement dataTypeElement = object.get(ITERABLE_VALUE_TYPE); + if (dataTypeElement == null || !dataTypeElement.isJsonPrimitive()) { + throw new IllegalArgumentException( + "Field \"" + ITERABLE_VALUE_TYPE + "\" is missing or has wrong type in Json object"); + } + + DataTypeInfo dataTypeInfo = DataTypeInfo.of(dataTypeElement.getAsString()); + + // Unpack data. + JsonElement dataElement = object.get(ITERABLE_VALUE); + if (dataElement == null || !dataElement.isJsonArray()) { + throw new IllegalArgumentException( + "Field \"" + ITERABLE_VALUE + "\" is missing or has wrong type in Json object"); + } + + return new DeserializedJsonArray(dataTypeInfo, dataElement.getAsJsonArray()); + } + + public static void main(String[] args) { + Set set1 = new HashSet<>(); + set1.add("One"); + set1.add(null); + + Set set2 = new HashSet<>(); + set2.add("Zero"); + set2.add("Two"); + + List> createdList = new ArrayList<>(); + createdList.add(set1); + createdList.add(set2); + + Gson GSON = new GsonBuilder().serializeNulls() + .registerTypeAdapter(DataTypeInfo.of(SortedSet.class).getType(), new SortedSetIOv1()) + .registerTypeAdapter(DataTypeInfo.of(Set.class).getType(), new SetIOv1()) + .registerTypeAdapter(DataTypeInfo.of(List.class).getType(), new ListIOv1()) + .setPrettyPrinting().create(); + + String testPath = Paths.get(System.getProperty("user.home"), "Downloads").toString(); + String file = Paths.get(testPath, "test-iterables.json").toString(); + try (FileWriter fileWriter = new FileWriter(file)) { + try (JsonWriter jsonWriter = GSON.newJsonWriter(fileWriter)) { + GSON.toJson(createdList, DataTypeInfo.forObject(createdList).getType(), jsonWriter); + fileWriter.write('\n'); + } + } catch (IOException e) { + e.printStackTrace(); + } + + try (JsonReader jsonReader = GSON.newJsonReader(new FileReader(file))) { + Iterable readList = GSON.fromJson(jsonReader, DataTypeInfo.of(List.class).getType()); + if (!readList.equals(createdList)) { + System.err.println("OUTPUT IS NOT EQUAL TO INPUT!!"); + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/IterableIOv2.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/IterableIOv2.java new file mode 100644 index 0000000..27eb7f9 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/IterableIOv2.java @@ -0,0 +1,217 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.Version; +import edu.jhuapl.ses.jsqrl.impl.Utilities; + +abstract class IterableIOv2 implements JsonSerializer> { + + /** + * Iterate over objects that may include implementations of Metadata and extract the common + * Metadata Version, if there is one. + * + * @param iterable the objects to examine + * @return the version that all the metadata share, or null if either there are no Metadata + * objects, if the Metadata objects do not share a common version, or if there is only one + * Metadata object + */ + public static Version getCommonVersion(Iterable iterable) { + Version result = null; + + int metadataCount = 0; + for (Object item : iterable) { + if (item instanceof Metadata) { + ++metadataCount; + Version itemVersion = ((Metadata) item).getVersion(); + + if (result == null) { + // First Metadata found -- provisionally make its Version the result. + result = itemVersion; + + } else if (!result.equals(itemVersion)) { + // More than one Metadata was found, and it has a different Version from the provisional + // result. Discard the provisional result and don't look any further. + result = null; + break; + + } + } + } + + // If only one metadata object is present, do not return a "common" version. + if (metadataCount == 1) { + result = null; + } + + return result; + } + + protected IterableIOv2() { + + } + + private static final String VALUE_KEY = "value"; + private static final Key PRIVATE_NULL_KEY = Key.of("PRIVATE NULL KEY"); + + @Override + public JsonElement serialize(Iterable src, @SuppressWarnings("unused") Type typeOfSrc, + JsonSerializationContext context) { + + // First pass: if any entries are found, determine types of key and value based on the type of + // the first non-null element. + // Note that although iterableTypeInfo and iterableTypeKey are very similar, there are nuances + // that lead to the need to treat them slightly differently with respect to how null values are + // handled/categorized. + DataTypeInfo iterableTypeInfo = DataTypeInfo.NULL; // Meaning an entry is a null pointer. + Key iterableTypeKey = null; // Meaning the instance getter could not decode this. + boolean sameType = true; + for (Object item : src) { + DataTypeInfo valueInfo = DataTypeInfo.forObject(item); + if (iterableTypeInfo == DataTypeInfo.NULL) { + iterableTypeInfo = valueInfo; + } else if (valueInfo != DataTypeInfo.NULL && valueInfo != iterableTypeInfo) { + sameType = false; + break; + } + + // See if there is a provider for the type indicated by this key. + Key valueTypeKey = Utilities.provideTypeKeyIfPossible(item); + if (valueTypeKey == null) { + // Use a null object rather than null pointer to distinguish between the cases "no provider" + // and "no provider YET". + valueTypeKey = PRIVATE_NULL_KEY; + } + + if (iterableTypeKey == null) { + iterableTypeKey = valueTypeKey; + } else if (iterableTypeKey == PRIVATE_NULL_KEY || valueTypeKey == PRIVATE_NULL_KEY) { + // Use object equality for comparisons involving the private null key. This is to prevent a + // name collision should the user happen to define type key with the same name as the + // private null key. + if (iterableTypeKey != valueTypeKey) { + sameType = false; + break; + } + } else if (!iterableTypeKey.equals(valueTypeKey)) { + sameType = false; + break; + } + } + + // The private null key is just for use within this method, so replace it with null before + // continuing. + if (iterableTypeKey == PRIVATE_NULL_KEY) { + iterableTypeKey = null; + } + + // Extract metadata version, if any. + Version commonVersion = getCommonVersion(src); + boolean excludeMetadataVersionInValues = commonVersion != null; + + // Second pass: write items, either with or without their types depending on whether they are + // the same. + JsonArray jsonArray = new JsonArray(); + for (Object item : src) { + JsonElement encodedItem = + sameType ? GsonElement.encodeItem(item, excludeMetadataVersionInValues, context) + : GsonElement.encodeItemWithType(item, excludeMetadataVersionInValues, context); + jsonArray.add(encodedItem); + } + + // Put iterable metadata and data into the resultant object. + JsonObject result = new JsonObject(); + if (sameType) { + String iterableTypeId = + iterableTypeKey != null ? iterableTypeKey.getId() : iterableTypeInfo.getTypeId(); + GsonElement.encodeTypeInfo(iterableTypeId, result); + } + if (excludeMetadataVersionInValues) { + MetadataIOv2.encodeVersion(commonVersion, result); + } + result.add(VALUE_KEY, jsonArray); + + return result; + } + + protected List deserialize(JsonObject encodedIterable, JsonDeserializationContext context) { + Key typeKey = GsonElement.decodeTypeInfo(encodedIterable); + + Version commonVersion = MetadataIOv2.decodeVersion(encodedIterable); + + JsonArray encodedArray = encodedIterable.get(VALUE_KEY).getAsJsonArray(); + + List result = new ArrayList<>(); + for (JsonElement encodedItem : encodedArray) { + if (typeKey != null) { + result.add(GsonElement.decodeItem(encodedItem, typeKey, commonVersion, context)); + } else { + result.add(GsonElement.decodeItem(encodedItem, commonVersion, context)); + } + } + return result; + } + + public static void main(String[] args) { + Set set1 = new HashSet<>(); + set1.add("One"); + set1.add(null); + + Set set2 = new HashSet<>(); + set2.add("Zero"); + set2.add("Two"); + + List> createdList = new ArrayList<>(); + createdList.add(set1); + createdList.add(set2); + + Gson GSON = new GsonBuilder().serializeNulls() + .registerTypeAdapter(DataTypeInfo.of(SortedSet.class).getType(), new SortedSetIOv2()) + .registerTypeAdapter(DataTypeInfo.of(Set.class).getType(), new SetIOv2()) + .registerTypeAdapter(DataTypeInfo.of(List.class).getType(), new ListIOv2()) + .setPrettyPrinting().create(); + + String testPath = Paths.get(System.getProperty("user.home"), "Downloads").toString(); + String file = Paths.get(testPath, "test-iterables.json").toString(); + try (FileWriter fileWriter = new FileWriter(file)) { + try (JsonWriter jsonWriter = GSON.newJsonWriter(fileWriter)) { + GSON.toJson(createdList, DataTypeInfo.forObject(createdList).getType(), jsonWriter); + fileWriter.write('\n'); + } + } catch (IOException e) { + e.printStackTrace(); + } + + try (JsonReader jsonReader = GSON.newJsonReader(new FileReader(file))) { + Iterable readList = GSON.fromJson(jsonReader, DataTypeInfo.of(List.class).getType()); + if (!readList.equals(createdList)) { + System.err.println("OUTPUT IS NOT EQUAL TO INPUT!!"); + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/ListIO.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/ListIO.java new file mode 100644 index 0000000..fa90749 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/ListIO.java @@ -0,0 +1,9 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.util.List; + +import com.google.gson.JsonDeserializer; + +interface ListIO extends JsonDeserializer> { + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/ListIOv1.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/ListIOv1.java new file mode 100644 index 0000000..cf576a5 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/ListIOv1.java @@ -0,0 +1,35 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +final class ListIOv1 extends IterableIOv1 implements ListIO { + + ListIOv1() { + + } + + @Override + public List deserialize(JsonElement jsonElement, @SuppressWarnings("unused") Type typeOfT, + JsonDeserializationContext context) throws JsonParseException { + DeserializedJsonArray array = unpack(jsonElement); + DataTypeInfo dataInfo = array.dataTypeInfo; + JsonArray jsonArray = array.jsonArray; + + // Create output data object. + List result = new ArrayList<>(); + + Type valueType = dataInfo.getType(); + for (JsonElement entryElement : jsonArray) { + result.add(context.deserialize(entryElement, valueType)); + } + return result; + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/ListIOv2.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/ListIOv2.java new file mode 100644 index 0000000..8bb453c --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/ListIOv2.java @@ -0,0 +1,24 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; +import java.util.List; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; + +final class ListIOv2 extends IterableIOv2 implements ListIO { + + ListIOv2() { + + } + + @Override + public List deserialize(JsonElement jsonElement, @SuppressWarnings("unused") Type typeOfT, + JsonDeserializationContext context) { + Preconditions.checkArgument(jsonElement.isJsonObject()); + + return deserialize(jsonElement.getAsJsonObject(), context); + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/MapBaseIOv1.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/MapBaseIOv1.java new file mode 100644 index 0000000..8941e16 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/MapBaseIOv1.java @@ -0,0 +1,162 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.SortedMap; +import java.util.TreeMap; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +abstract class MapBaseIOv1 implements JsonSerializer> { + /** + * Internal utility class; just here so the unpack method can return all the distinct pieces of + * information contained in the input JsonElement. + */ + protected static class DeserializedJsonObject { + protected final DataTypeInfo keyTypeInfo; + protected final DataTypeInfo valueTypeInfo; + protected final JsonObject jsonMap; + + protected DeserializedJsonObject(DataTypeInfo keyTypeInfo, DataTypeInfo valueTypeInfo, + JsonObject jsonMap) { + this.keyTypeInfo = keyTypeInfo; + this.valueTypeInfo = valueTypeInfo; + this.jsonMap = jsonMap; + } + } + + private static final String MAP_KEY_TYPE = "keyType"; + private static final String MAP_VALUE_TYPE = "valueType"; + private static final String MAP_VALUE = "value"; + + protected MapBaseIOv1() { + + } + + @Override + public JsonElement serialize(Map src, @SuppressWarnings("unused") Type typeOfSrc, + JsonSerializationContext context) { + JsonObject result = new JsonObject(); + + // First pass: if any entries are found, determine types of key and value. + DataTypeInfo keyInfo = DataTypeInfo.NULL; + DataTypeInfo valueInfo = DataTypeInfo.NULL; + for (Entry entry : src.entrySet()) { + if (keyInfo == DataTypeInfo.NULL) { + keyInfo = DataTypeInfo.forObject(entry.getKey()); + } + if (valueInfo == DataTypeInfo.NULL) { + valueInfo = DataTypeInfo.forObject(entry.getValue()); + } + if (keyInfo != DataTypeInfo.NULL && valueInfo != DataTypeInfo.NULL) { + break; + } + } + + // Second pass: write the map entries to a JsonObject. + JsonObject dest = new JsonObject(); + Type valueType = valueInfo.getType(); + + for (Entry entry : src.entrySet()) { + Object key = entry.getKey(); + Object value = entry.getValue(); + dest.add(key != null ? key.toString() : JsonNull.INSTANCE.toString(), + context.serialize(value, valueType)); + } + + // Put type information about key and value, along with the map entries + // into the resultant object. + result.add(MAP_KEY_TYPE, context.serialize(keyInfo.getTypeId())); + result.add(MAP_VALUE_TYPE, context.serialize(valueInfo.getTypeId())); + result.add(MAP_VALUE, dest); + + return result; + } + + public DeserializedJsonObject unpack(JsonElement jsonElement) { + if (!jsonElement.isJsonObject()) { + throw new IllegalArgumentException(); + } + + JsonObject object = jsonElement.getAsJsonObject(); + + // Unpack metadata. + JsonElement keyTypeElement = object.get(MAP_KEY_TYPE); + if (keyTypeElement == null || !keyTypeElement.isJsonPrimitive()) { + throw new IllegalArgumentException( + "Field \"" + MAP_KEY_TYPE + "\" is missing or has wrong type in Json object"); + } + + JsonElement valueTypeElement = object.get(MAP_VALUE_TYPE); + if (valueTypeElement == null || !valueTypeElement.isJsonPrimitive()) { + throw new IllegalArgumentException( + "Field \"" + MAP_VALUE_TYPE + "\" is missing or has wrong type in Json object"); + } + + DataTypeInfo keyInfo = DataTypeInfo.of(keyTypeElement.getAsString()); + DataTypeInfo valueInfo = DataTypeInfo.of(valueTypeElement.getAsString()); + + // Unpack data. + JsonElement dataElement = object.get(MAP_VALUE); + if (dataElement == null || !dataElement.isJsonObject()) { + throw new IllegalArgumentException( + "Field \"" + MAP_VALUE + "\" is missing or has wrong type in Json object"); + } + + return new DeserializedJsonObject(keyInfo, valueInfo, dataElement.getAsJsonObject()); + } + + public static void main(String[] args) { + Map map1 = new HashMap<>(); + map1.put(1, "One"); + map1.put(null, "Null"); + + Map map2 = new HashMap<>(); + map2.put(0, "Zero"); + map2.put(2, null); + + SortedMap> createdMap = new TreeMap<>(); + createdMap.put("Map one", map1); + createdMap.put("Map two", map2); + + Gson GSON = new GsonBuilder().serializeNulls() + .registerTypeAdapter(DataTypeInfo.of(SortedMap.class).getType(), new SortedMapIOv1()) + .registerTypeAdapter(DataTypeInfo.of(Map.class).getType(), new MapIOv1()) + .setPrettyPrinting().create(); + + String testPath = Paths.get(System.getProperty("user.home"), "Downloads").toString(); + String file = Paths.get(testPath, "test-map.json").toString(); + try (FileWriter fileWriter = new FileWriter(file)) { + try (JsonWriter jsonWriter = GSON.newJsonWriter(fileWriter)) { + GSON.toJson(createdMap, DataTypeInfo.forObject(createdMap).getType(), jsonWriter); + fileWriter.write('\n'); + } + } catch (IOException e) { + e.printStackTrace(); + } + + try (JsonReader jsonReader = GSON.newJsonReader(new FileReader(file))) { + Map readMap = GSON.fromJson(jsonReader, DataTypeInfo.of(Map.class).getType()); + if (!readMap.equals(createdMap)) { + System.err.println("OUTPUT IS NOT EQUAL TO INPUT!!"); + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/MapBaseIOv2.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/MapBaseIOv2.java new file mode 100644 index 0000000..7219ad2 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/MapBaseIOv2.java @@ -0,0 +1,207 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +import com.google.common.base.Preconditions; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Version; +import edu.jhuapl.ses.jsqrl.impl.Utilities; + +abstract class MapBaseIOv2 implements JsonSerializer> { + + protected static Object decodeKeyString(String keyString, DataTypeInfo typeInfo, + JsonDeserializationContext context) { + JsonElement keyElement = + keyString.equals("null") ? JsonNull.INSTANCE : new JsonPrimitive(keyString); + return context.deserialize(keyElement, typeInfo.getType()); + } + + static final String KEY_TYPE_KEY = "keyType"; + static final String VALUE_KEY = "value"; + private static final Key PRIVATE_NULL_KEY = Key.of("PRIVATE NULL KEY"); + + protected MapBaseIOv2() { + + } + + @Override + public JsonElement serialize(Map src, @SuppressWarnings("unused") Type typeOfSrc, + JsonSerializationContext context) { + Preconditions.checkNotNull(src); + Preconditions.checkNotNull(context); + + Set keys = src.keySet(); + + // First pass: if any entries are found, determine types of key and value. + List values = new ArrayList<>(); + DataTypeInfo mapKeyInfo = DataTypeInfo.NULL; + // Note that although mapValueInfo and mapValueTypeKey are very similar, there are nuances + // that lead to the need to treat them slightly differently with respect to how null values are + // handled/categorized. + DataTypeInfo mapValueInfo = DataTypeInfo.NULL; // Meaning a value is a null pointer. + Key mapValueTypeKey = null; // Meaning the instance getter could not decode this. + boolean sameValueType = keys.size() > 1; + + for (Object key : keys) { + DataTypeInfo keyInfo = DataTypeInfo.forObject(key); + Object value = src.get(key); + values.add(value); + + if (mapKeyInfo == DataTypeInfo.NULL) { + mapKeyInfo = keyInfo; + } else if (keyInfo != DataTypeInfo.NULL && keyInfo != mapKeyInfo) { + // This could be supported if anyone ever needs it. + throw new IllegalStateException("Cannot store a key of type " + keyInfo.getTypeId() + + " in a map inferred to have keys of type " + mapKeyInfo.getTypeId()); + } + + DataTypeInfo valueInfo = DataTypeInfo.forObject(value); + if (mapValueInfo == DataTypeInfo.NULL) { + mapValueInfo = valueInfo; + } else if (valueInfo != DataTypeInfo.NULL && valueInfo != mapValueInfo) { + sameValueType = false; + } + + // See if there is a provider for the type indicated by this key. + Key valueTypeKey = Utilities.provideTypeKeyIfPossible(value); + if (valueTypeKey == null) { + // Use a null object rather than null pointer to distinguish between the cases "no provider" + // and "no provider YET". + valueTypeKey = PRIVATE_NULL_KEY; + } + + if (mapValueTypeKey == null) { + mapValueTypeKey = valueTypeKey; + } else if (mapValueTypeKey == PRIVATE_NULL_KEY || valueTypeKey == PRIVATE_NULL_KEY) { + // Use object equality for comparisons involving the private null key. This is to prevent a + // name collision should the user happen to define type key with the same name as the + // private null key. + if (mapValueTypeKey != valueTypeKey) { + sameValueType = false; + break; + } + } else if (!mapValueTypeKey.equals(valueTypeKey)) { + sameValueType = false; + } + } + + // The private null key is just for use within this method, so replace it with null before + // continuing. + if (mapValueTypeKey == PRIVATE_NULL_KEY) { + mapValueTypeKey = null; + } + + // Extract metadata version, if any. + Version commonVersion = IterableIOv2.getCommonVersion(values); + boolean excludeMetadataVersionInValues = commonVersion != null; + + // Second pass: write the map entries to a JsonObject. + JsonObject encodedMap = new JsonObject(); + for (Object metadataKey : keys) { + Object key = metadataKey != null ? metadataKey : JsonNull.INSTANCE; + Object value = src.get(metadataKey); + + JsonElement encodedValue = + sameValueType ? GsonElement.encodeItem(value, excludeMetadataVersionInValues, context) + : GsonElement.encodeItemWithType(value, excludeMetadataVersionInValues, context); + + encodedMap.add(key.toString(), encodedValue); + } + + // Put type information about key and value, along with the map entries + // into the resultant object. + JsonObject result = new JsonObject(); + result.addProperty(KEY_TYPE_KEY, mapKeyInfo.getTypeId()); + if (sameValueType) { + String mapValueTypeId = + mapValueTypeKey != null ? mapValueTypeKey.getId() : mapValueInfo.getTypeId(); + GsonElement.encodeTypeInfo(mapValueTypeId, result); + } + if (excludeMetadataVersionInValues) { + MetadataIOv2.encodeVersion(commonVersion, result); + } + result.add(VALUE_KEY, encodedMap); + + return result; + } + + protected void deserialize(JsonObject encodedItem, JsonDeserializationContext context, + Map map) { + DataTypeInfo mapKeyInfo = DataTypeInfo.of(encodedItem.get(KEY_TYPE_KEY).getAsString()); + Key mapValueType = GsonElement.decodeTypeInfo(encodedItem); + Version commonVersion = MetadataIOv2.decodeVersion(encodedItem); + JsonObject encodedMap = encodedItem.get(VALUE_KEY).getAsJsonObject(); + + for (Entry entry : encodedMap.entrySet()) { + Object key = decodeKeyString(entry.getKey(), mapKeyInfo, context); + Object value = mapValueType != null + ? GsonElement.decodeItem(entry.getValue(), mapValueType, commonVersion, context) + : GsonElement.decodeItem(entry.getValue(), commonVersion, context); + map.put(key, value); + } + } + + public static void main(String[] args) { + Map map1 = new HashMap<>(); + map1.put(1, "One"); + map1.put(null, "Null"); + + Map map2 = new HashMap<>(); + map2.put(0, "Zero"); + map2.put(2, null); + + SortedMap> createdMap = new TreeMap<>(); + createdMap.put("Map one", map1); + createdMap.put("Map two", map2); + + Gson GSON = new GsonBuilder().serializeNulls() + .registerTypeAdapter(DataTypeInfo.of(SortedMap.class).getType(), new SortedMapIOv2()) + .registerTypeAdapter(DataTypeInfo.of(Map.class).getType(), new MapIOv2()) + .setPrettyPrinting().create(); + + String testPath = Paths.get(System.getProperty("user.home"), "Downloads").toString(); + String file = Paths.get(testPath, "test-map.json").toString(); + try (FileWriter fileWriter = new FileWriter(file)) { + try (JsonWriter jsonWriter = GSON.newJsonWriter(fileWriter)) { + GSON.toJson(createdMap, DataTypeInfo.forObject(createdMap).getType(), jsonWriter); + fileWriter.write('\n'); + } + } catch (IOException e) { + e.printStackTrace(); + } + + try (JsonReader jsonReader = GSON.newJsonReader(new FileReader(file))) { + Map readMap = GSON.fromJson(jsonReader, DataTypeInfo.of(Map.class).getType()); + if (!readMap.equals(createdMap)) { + System.err.println("OUTPUT IS NOT EQUAL TO INPUT!!"); + } + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/MapIO.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/MapIO.java new file mode 100644 index 0000000..eb60279 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/MapIO.java @@ -0,0 +1,16 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; +import java.util.Map; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; + + +interface MapIO extends JsonDeserializer> { + + @Override + Map deserialize(JsonElement jsonElement, Type typeOfT, JsonDeserializationContext context); + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/MapIOv1.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/MapIOv1.java new file mode 100644 index 0000000..d78677c --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/MapIOv1.java @@ -0,0 +1,37 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; + +final class MapIOv1 extends MapBaseIOv1 implements MapIO { + @Override + public Map deserialize(JsonElement jsonElement, @SuppressWarnings("unused") Type typeOfT, + JsonDeserializationContext context) throws JsonParseException { + Map result = new HashMap<>(); + + DeserializedJsonObject object = unpack(jsonElement); + Type keyType = object.keyTypeInfo.getType(); + Type valueType = object.valueTypeInfo.getType(); + + for (Entry entry : object.jsonMap.getAsJsonObject().entrySet()) { + String keyString = entry.getKey(); + JsonElement keyElement = + keyString.equals("null") ? JsonNull.INSTANCE : new JsonPrimitive(keyString); + JsonElement valueElement = entry.getValue(); + + result.put(context.deserialize(keyElement, keyType), + context.deserialize(valueElement, valueType)); + } + + return result; + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/MapIOv2.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/MapIOv2.java new file mode 100644 index 0000000..4ce08f2 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/MapIOv2.java @@ -0,0 +1,25 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; + +final class MapIOv2 extends MapBaseIOv2 implements MapIO { + + @Override + public Map deserialize(JsonElement jsonElement, @SuppressWarnings("unused") Type typeOfT, + JsonDeserializationContext context) { + + Preconditions.checkArgument(jsonElement.isJsonObject()); + + Map result = new LinkedHashMap<>(); + deserialize(jsonElement.getAsJsonObject(), context, result); + + return result; + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/MetadataIO.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/MetadataIO.java new file mode 100644 index 0000000..0719e3c --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/MetadataIO.java @@ -0,0 +1,10 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonSerializer; + +import edu.jhuapl.ses.jsqrl.api.Metadata; + +interface MetadataIO extends JsonSerializer, JsonDeserializer { + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/MetadataIOv1.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/MetadataIOv1.java new file mode 100644 index 0000000..1b7f184 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/MetadataIOv1.java @@ -0,0 +1,75 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; +import java.util.Iterator; +import java.util.Map.Entry; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; + +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.Version; +import edu.jhuapl.ses.jsqrl.impl.SettableMetadata; + +final class MetadataIOv1 implements MetadataIO { + @Override + public JsonElement serialize(Metadata src, Type typeOfSrc, JsonSerializationContext context) { + Preconditions.checkNotNull(src); + Preconditions.checkArgument(DataTypeInfo.METADATA.getType().equals(typeOfSrc)); + Preconditions.checkNotNull(context); + + JsonArray array = new JsonArray(); + array.add(context.serialize(src.getVersion(), DataTypeInfo.VERSION.getType())); + + JsonObject jsonMetadata = new JsonObject(); + for (Key key : src.getKeys()) { + Object value = src.get(key); + jsonMetadata.add(key.getId(), + context.serialize(GsonElement.of(value), DataTypeInfo.ELEMENT.getType())); + } + + array.add(jsonMetadata); + + return array; + } + + @Override + public Metadata deserialize(JsonElement jsonSrc, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + Preconditions.checkNotNull(jsonSrc); + Preconditions.checkArgument(jsonSrc.isJsonArray()); + Preconditions.checkArgument(DataTypeInfo.METADATA.getType().equals(typeOfT)); + Preconditions.checkNotNull(context); + + JsonArray jsonArray = jsonSrc.getAsJsonArray(); + JsonElement jsonElement = null; + Iterator iterator = jsonArray.iterator(); + + if (!iterator.hasNext()) { + throw new IllegalArgumentException(); + } + jsonElement = iterator.next(); + + Version version = context.deserialize(jsonElement, DataTypeInfo.VERSION.getType()); + + final SettableMetadata metadata = SettableMetadata.of(version); + if (!iterator.hasNext()) { + throw new IllegalArgumentException(); + } + jsonElement = iterator.next(); + JsonObject jsonMetadata = jsonElement.getAsJsonObject(); + for (Entry entry : jsonMetadata.entrySet()) { + Key key = Key.of(entry.getKey()); + GsonElement element = context.deserialize(entry.getValue(), DataTypeInfo.ELEMENT.getType()); + metadata.put(key, element.getValue()); + } + return metadata; + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/MetadataIOv2.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/MetadataIOv2.java new file mode 100644 index 0000000..d8df240 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/MetadataIOv2.java @@ -0,0 +1,159 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; + +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.Version; +import edu.jhuapl.ses.jsqrl.impl.SettableMetadata; + +final class MetadataIOv2 implements MetadataIO { + + private static final String VERSION_KEY = "metadata.Version"; + + public static JsonObject encodeWithoutVersion(Metadata src, JsonSerializationContext context) { + JsonObject result = encodeWithVersion(src, context); + + result.remove(DataTypeInfo.VERSION.getTypeId()); + + return result; + } + + public static JsonObject encodeWithVersion(Metadata src, JsonSerializationContext context) { + Map map = new LinkedHashMap<>(); + for (Key key : src.getKeys()) { + map.put(key.getId(), src.get(key)); + } + + // Use MapIO to do the detailed encoding. + MapIOv2 mapIo = new MapIOv2(); + JsonObject encodedMap = + mapIo.serialize(map, DataTypeInfo.MAP.getType(), context).getAsJsonObject(); + + // Pull out info needed to characterize the Metadata. (Skip the Key type -- known to be string). + JsonObject result = new JsonObject(); + result.add(DataTypeInfo.VERSION.getTypeId(), GsonVersionIO.encode(src.getVersion())); + if (encodedMap.has(GsonElement.VALUE_TYPE_KEY)) { + result.add(GsonElement.VALUE_TYPE_KEY, encodedMap.get(GsonElement.VALUE_TYPE_KEY)); + } + if (encodedMap.has(VERSION_KEY)) { + result.add(VERSION_KEY, encodedMap.get(VERSION_KEY)); + } + + // Rebundle the serialized map so it's stored in the same order as the metadata. + JsonObject keyValueObject = encodedMap.get(MapBaseIOv2.VALUE_KEY).getAsJsonObject(); + JsonObject keyValueObjectInOrder = new JsonObject(); + for (Key key : src.getKeys()) { + String keyId = key.getId(); + keyValueObjectInOrder.add(keyId, keyValueObject.get(keyId)); + } + + result.add(MapBaseIOv2.VALUE_KEY, keyValueObjectInOrder); + + return result; + } + + public static void encodeVersion(Version version, JsonObject object) { + object.add(VERSION_KEY, GsonVersionIO.encode(version)); + } + + public static Metadata decode(JsonObject jsonMetadata, Version version, + JsonDeserializationContext context) { + Preconditions.checkNotNull(jsonMetadata); + Preconditions.checkNotNull(version); + Preconditions.checkNotNull(context); + + // Reverse the encoding process. The ordered map is stored as the "value". + JsonObject keyValueObjectInOrder = jsonMetadata.get(MapBaseIOv2.VALUE_KEY).getAsJsonObject(); + + // Convert the supplied jsonMetadata object into the same format as an encoded map. + JsonObject encodedMap = new JsonObject(); + // Note the key type was not serialized but we need to add it here. + encodedMap.addProperty(MapBaseIOv2.KEY_TYPE_KEY, DataTypeInfo.STRING.getTypeId()); + if (jsonMetadata.has(GsonElement.VALUE_TYPE_KEY)) { + encodedMap.add(GsonElement.VALUE_TYPE_KEY, jsonMetadata.get(GsonElement.VALUE_TYPE_KEY)); + } + if (jsonMetadata.has(VERSION_KEY)) { + encodedMap.add(VERSION_KEY, jsonMetadata.get(VERSION_KEY)); + } + encodedMap.add(MapBaseIOv2.VALUE_KEY, keyValueObjectInOrder); + + // Now decode the synthesized encoded map. + MapIOv2 mapIo = new MapIOv2(); + @SuppressWarnings("unchecked") + Map map = + (Map) mapIo.deserialize(encodedMap, DataTypeInfo.MAP.getType(), context); + + // Finally use the key order from the serialized form to reconstruct the metadata object content + // in order. + SettableMetadata metadata = SettableMetadata.of(version); + for (Entry entry : keyValueObjectInOrder.entrySet()) { + String keyId = entry.getKey(); + // Pull the reconstructed elements of the metadata from the map. + metadata.put(Key.of(keyId), map.get(keyId)); + } + + return metadata; + } + + public static Metadata decodeWithVersion(JsonObject jsonMetadata, + JsonDeserializationContext context) { + Preconditions.checkNotNull(jsonMetadata); + + Version version = decodeVersion(jsonMetadata); + + return decode(jsonMetadata, version, context); + } + + public static Version decodeVersion(JsonObject object) { + Version result = null; + + if (object.has(VERSION_KEY)) { + result = GsonVersionIO.decode(object.get(VERSION_KEY).getAsJsonPrimitive()); + } else if (object.has(DataTypeInfo.VERSION.getTypeId())) { + result = + GsonVersionIO.decode(object.get(DataTypeInfo.VERSION.getTypeId()).getAsJsonPrimitive()); + } + + return result; + } + + /* + * (non-Javadoc) + * + * @see + * edu.jhuapl.ses.jsqrl.impl.gson.MetadataIO#serialize(edu.jhuapl.ses.jsqrl.api.Metadata, + * java.lang.reflect.Type, com.google.gson.JsonSerializationContext) + */ + @Override + public JsonElement serialize(Metadata src, Type typeOfSrc, JsonSerializationContext context) { + Preconditions.checkArgument(DataTypeInfo.METADATA.getType().equals(typeOfSrc)); + return encodeWithVersion(src, context); + } + + /* + * (non-Javadoc) + * + * @see edu.jhuapl.ses.jsqrl.impl.gson.MetadataIO#deserialize(com.google.gson.JsonElement, + * java.lang.reflect.Type, com.google.gson.JsonDeserializationContext) + */ + @Override + public Metadata deserialize(JsonElement jsonSrc, Type typeOfT, + JsonDeserializationContext context) { + Preconditions.checkNotNull(jsonSrc); + Preconditions.checkArgument(jsonSrc.isJsonObject()); + Preconditions.checkArgument(DataTypeInfo.METADATA.getType().equals(typeOfT)); + + return decodeWithVersion(jsonSrc.getAsJsonObject(), context); + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/ProxyIO.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/ProxyIO.java new file mode 100644 index 0000000..128d0e8 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/ProxyIO.java @@ -0,0 +1,8 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonSerializer; + +interface ProxyIO extends JsonSerializer, JsonDeserializer { + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/ProxyIOv1.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/ProxyIOv1.java new file mode 100644 index 0000000..0eedc79 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/ProxyIOv1.java @@ -0,0 +1,57 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; + +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.StorableAsMetadata; +import edu.jhuapl.ses.jsqrl.impl.InstanceGetter; + +final class ProxyIOv1 implements ProxyIO { + + @Override + public JsonElement serialize(Object src, @SuppressWarnings("unused") Type typeOfSrc, + JsonSerializationContext context) { + JsonObject object = new JsonObject(); + + Key key; + Metadata metadata; + if (src instanceof StorableAsMetadata) { + StorableAsMetadata storable = (StorableAsMetadata) src; + + key = storable.getKey(); + metadata = storable.store(); + } else { + @SuppressWarnings("unchecked") + Class objectType = (Class) src.getClass(); + InstanceGetter instanceGetter = InstanceGetter.defaultInstanceGetter(); + + key = instanceGetter.getKeyForType(objectType); + metadata = instanceGetter.providesMetadataFromGenericObject(objectType).provide(src); + } + + object.addProperty("proxiedType", key.getId()); + object.add("proxyMetadata", context.serialize(metadata, DataTypeInfo.METADATA.getType())); + + return object; + } + + @Override + public T deserialize(JsonElement json, @SuppressWarnings("unused") Type typeOfT, + JsonDeserializationContext context) throws JsonParseException { + JsonObject object = json.getAsJsonObject(); + Key proxyKey = Key.of(object.get("proxiedType").getAsString()); + Metadata objectMetadata = + context.deserialize(object.get("proxyMetadata"), DataTypeInfo.METADATA.getType()); + + return InstanceGetter.defaultInstanceGetter().providesGenericObjectFromMetadata(proxyKey) + .provide(objectMetadata); + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/ProxyIOv2.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/ProxyIOv2.java new file mode 100644 index 0000000..cd90620 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/ProxyIOv2.java @@ -0,0 +1,57 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; + +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.StorableAsMetadata; +import edu.jhuapl.ses.jsqrl.impl.InstanceGetter; + +final class ProxyIOv2 implements ProxyIO { + + @Override + public JsonElement serialize(Object src, @SuppressWarnings("unused") Type typeOfSrc, + JsonSerializationContext context) { + JsonObject object = new JsonObject(); + + Key key; + Metadata metadata; + if (src instanceof StorableAsMetadata) { + StorableAsMetadata storable = (StorableAsMetadata) src; + + key = storable.getKey(); + metadata = storable.store(); + } else { + @SuppressWarnings("unchecked") + Class objectType = (Class) src.getClass(); + InstanceGetter instanceGetter = InstanceGetter.defaultInstanceGetter(); + + key = instanceGetter.getKeyForType(objectType); + metadata = instanceGetter.providesMetadataFromGenericObject(objectType).provide(src); + } + + object.addProperty("proxiedType", key.getId()); + object.add("proxyMetadata", context.serialize(metadata, DataTypeInfo.METADATA.getType())); + + return object; + } + + @Override + public T deserialize(JsonElement json, @SuppressWarnings("unused") Type typeOfT, + JsonDeserializationContext context) throws JsonParseException { + JsonObject object = json.getAsJsonObject(); + Key proxyKey = Key.of(object.get("proxiedType").getAsString()); + Metadata objectMetadata = + context.deserialize(object.get("proxyMetadata"), DataTypeInfo.METADATA.getType()); + + return InstanceGetter.defaultInstanceGetter().providesGenericObjectFromMetadata(proxyKey) + .provide(objectMetadata); + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/SerializableIO.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/SerializableIO.java new file mode 100644 index 0000000..1f80f63 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/SerializableIO.java @@ -0,0 +1,66 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Type; +import java.util.Base64; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import com.google.common.base.Preconditions; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +public class SerializableIO + implements JsonSerializer, JsonDeserializer { + + public SerializableIO() { + super(); + } + + @Override + public JsonElement serialize(Serializable src, Type typeOfSrc, JsonSerializationContext context) { + String encodedString; + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + try (ObjectOutputStream oos = + new ObjectOutputStream(/* new GZIPOutputStream( */new GZIPOutputStream(baos))) { + oos.writeObject(src); + } + encodedString = Base64.getEncoder().encodeToString(baos.toByteArray()); + } catch (Exception e) { + encodedString = null; + } + + return encodedString != null ? new JsonPrimitive(encodedString) : JsonNull.INSTANCE; + } + + @Override + public Serializable deserialize(JsonElement jsonElement, Type typeOfT, + JsonDeserializationContext context) throws JsonParseException { + Preconditions.checkArgument(jsonElement.isJsonPrimitive()); + + String encodedString = jsonElement.getAsString(); + + byte[] data = Base64.getDecoder().decode(encodedString); + + Serializable serializable; + try (ObjectInputStream ois = + new ObjectInputStream(new GZIPInputStream(new ByteArrayInputStream(data)))) { + serializable = (Serializable) ois.readObject(); + } catch (Exception e) { + serializable = null; + } + + + return serializable; + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/Serializers.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/Serializers.java new file mode 100644 index 0000000..7274816 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/Serializers.java @@ -0,0 +1,102 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.io.File; +import java.io.IOException; + +import com.google.common.base.Preconditions; + +import edu.jhuapl.ses.jsqrl.api.Key; +import edu.jhuapl.ses.jsqrl.api.Metadata; +import edu.jhuapl.ses.jsqrl.api.MetadataManager; +import edu.jhuapl.ses.jsqrl.api.Serializer; +import edu.jhuapl.ses.jsqrl.api.Version; +import edu.jhuapl.ses.jsqrl.impl.FixedMetadata; +import edu.jhuapl.ses.jsqrl.impl.SettableMetadata; +import edu.jhuapl.ses.jsqrl.impl.gson.GsonSerializer; + +public class Serializers { + private static final Serializer INSTANCE = GsonSerializer.of(); + + public static Serializer getDefault() { + return INSTANCE; + } + + public static Serializer of() { + return GsonSerializer.of(); + } + + public static void serialize(String metadataId, MetadataManager manager, File file) + throws IOException { + Preconditions.checkNotNull(metadataId); + Preconditions.checkNotNull(manager); + Preconditions.checkNotNull(file); + + Serializer serializer = of(); + serializer.register(Key.of(metadataId), manager); + serializer.save(file); + } + + public static void serialize(String metadataId, Metadata metadata, File file) throws IOException { + Preconditions.checkNotNull(metadataId); + Preconditions.checkNotNull(metadata); + Preconditions.checkNotNull(file); + + serialize(metadataId, new MetadataManager() { + + @Override + public Metadata store() { + return metadata; + } + + @Override + public void retrieve(@SuppressWarnings("unused") Metadata source) { + throw new UnsupportedOperationException(); + } + + }, file); + } + + public static void deserialize(File file, String metadataId, MetadataManager manager) + throws IOException { + Preconditions.checkNotNull(file); + Preconditions.checkNotNull(metadataId); + Preconditions.checkNotNull(manager); + + Serializer serializer = of(); + Key key = Key.of(metadataId); + serializer.register(key, manager); + serializer.load(file); + serializer.deregister(key); + } + + public static FixedMetadata deserialize(File file, String metadataId) throws IOException { + Preconditions.checkNotNull(file); + Preconditions.checkNotNull(metadataId); + + class DirectMetadataManager implements MetadataManager { + final SettableMetadata metadata = SettableMetadata.of(Version.of(0, 1)); + + @Override + public Metadata store() { + throw new UnsupportedOperationException(); + } + + @Override + public void retrieve(Metadata source) { + for (Key key : source.getKeys()) { + put(key, source.get(key), metadata); + } + } + + } + + DirectMetadataManager manager = new DirectMetadataManager(); + deserialize(file, metadataId, manager); + return FixedMetadata.of(manager.metadata); + } + + @SuppressWarnings("unchecked") + private static void put(Key key, T object, SettableMetadata metadata) { + metadata.put((Key) key, object); + } +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/SetIO.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/SetIO.java new file mode 100644 index 0000000..610a785 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/SetIO.java @@ -0,0 +1,9 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.util.Set; + +import com.google.gson.JsonDeserializer; + +interface SetIO extends JsonDeserializer> { + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/SetIOv1.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/SetIOv1.java new file mode 100644 index 0000000..ca2b559 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/SetIOv1.java @@ -0,0 +1,30 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; +import java.util.HashSet; +import java.util.Set; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +final class SetIOv1 extends IterableIOv1 implements SetIO { + @Override + public Set deserialize(JsonElement jsonElement, @SuppressWarnings("unused") Type typeOfT, + JsonDeserializationContext context) throws JsonParseException { + DeserializedJsonArray array = unpack(jsonElement); + DataTypeInfo dataInfo = array.dataTypeInfo; + JsonArray jsonArray = array.jsonArray; + + // Create output data object. + Set result = new HashSet<>(); + + Type valueType = dataInfo.getType(); + for (JsonElement entryElement : jsonArray) { + result.add(context.deserialize(entryElement, valueType)); + } + return result; + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/SetIOv2.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/SetIOv2.java new file mode 100644 index 0000000..c6e3d36 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/SetIOv2.java @@ -0,0 +1,21 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; +import java.util.LinkedHashSet; +import java.util.Set; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; + +final class SetIOv2 extends IterableIOv2 implements SetIO { + + @Override + public Set deserialize(JsonElement jsonElement, @SuppressWarnings("unused") Type typeOfT, + JsonDeserializationContext context) { + Preconditions.checkArgument(jsonElement.isJsonObject()); + + return new LinkedHashSet<>(deserialize(jsonElement.getAsJsonObject(), context)); + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedMapIO.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedMapIO.java new file mode 100644 index 0000000..f7de95f --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedMapIO.java @@ -0,0 +1,10 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.util.SortedMap; + +import com.google.gson.JsonDeserializer; + + +interface SortedMapIO extends JsonDeserializer> { + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedMapIOv1.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedMapIOv1.java new file mode 100644 index 0000000..7d0eb14 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedMapIOv1.java @@ -0,0 +1,38 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; +import java.util.Map.Entry; +import java.util.SortedMap; +import java.util.TreeMap; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; + +final class SortedMapIOv1 extends MapBaseIOv1 implements SortedMapIO { + @Override + public SortedMap deserialize(JsonElement jsonElement, + @SuppressWarnings("unused") Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + SortedMap result = new TreeMap<>(); + + DeserializedJsonObject object = unpack(jsonElement); + Type keyType = object.keyTypeInfo.getType(); + Type valueType = object.valueTypeInfo.getType(); + + for (Entry entry : object.jsonMap.getAsJsonObject().entrySet()) { + String keyString = entry.getKey(); + JsonElement keyElement = + keyString.equals("null") ? JsonNull.INSTANCE : new JsonPrimitive(keyString); + JsonElement valueElement = entry.getValue(); + + result.put(context.deserialize(keyElement, keyType), + context.deserialize(valueElement, valueType)); + } + + return result; + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedMapIOv2.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedMapIOv2.java new file mode 100644 index 0000000..5a34527 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedMapIOv2.java @@ -0,0 +1,25 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; +import java.util.SortedMap; +import java.util.TreeMap; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; + +final class SortedMapIOv2 extends MapBaseIOv2 implements SortedMapIO { + + @Override + public SortedMap deserialize(JsonElement jsonElement, + @SuppressWarnings("unused") Type typeOfT, JsonDeserializationContext context) { + + Preconditions.checkArgument(jsonElement.isJsonObject()); + + SortedMap result = new TreeMap<>(); + deserialize(jsonElement.getAsJsonObject(), context, result); + + return result; + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedSetIO.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedSetIO.java new file mode 100644 index 0000000..0fd3557 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedSetIO.java @@ -0,0 +1,10 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.util.SortedSet; + +import com.google.gson.JsonDeserializer; + + +interface SortedSetIO extends JsonDeserializer> { + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedSetIOv1.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedSetIOv1.java new file mode 100644 index 0000000..5465500 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedSetIOv1.java @@ -0,0 +1,30 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; +import java.util.SortedSet; +import java.util.TreeSet; + +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; + +final class SortedSetIOv1 extends IterableIOv1 implements SortedSetIO { + @Override + public SortedSet deserialize(JsonElement jsonElement, @SuppressWarnings("unused") Type typeOfT, + JsonDeserializationContext context) throws JsonParseException { + DeserializedJsonArray array = unpack(jsonElement); + DataTypeInfo dataInfo = array.dataTypeInfo; + JsonArray jsonArray = array.jsonArray; + + // Create output data object. + SortedSet result = new TreeSet<>(); + + Type valueType = dataInfo.getType(); + for (JsonElement entryElement : jsonArray) { + result.add(context.deserialize(entryElement, valueType)); + } + return result; + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedSetIOv2.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedSetIOv2.java new file mode 100644 index 0000000..4eb3fc5 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/SortedSetIOv2.java @@ -0,0 +1,21 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +import java.lang.reflect.Type; +import java.util.SortedSet; +import java.util.TreeSet; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; + +final class SortedSetIOv2 extends IterableIOv2 implements SortedSetIO { + + @Override + public SortedSet deserialize(JsonElement jsonElement, @SuppressWarnings("unused") Type typeOfT, + JsonDeserializationContext context) { + Preconditions.checkArgument(jsonElement.isJsonObject()); + + return new TreeSet<>(deserialize(jsonElement.getAsJsonObject(), context)); + } + +} diff --git a/src/edu/jhuapl/ses/jsqrl/impl/gson/Spud.java b/src/edu/jhuapl/ses/jsqrl/impl/gson/Spud.java new file mode 100644 index 0000000..5a98035 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/impl/gson/Spud.java @@ -0,0 +1,6 @@ +package edu.jhuapl.ses.jsqrl.impl.gson; + +public class Spud +{ + +} diff --git a/src/edu/jhuapl/ses/jsqrl/package-info.java b/src/edu/jhuapl/ses/jsqrl/package-info.java new file mode 100644 index 0000000..7eafe92 --- /dev/null +++ b/src/edu/jhuapl/ses/jsqrl/package-info.java @@ -0,0 +1,43 @@ +/** + * Core implementation of the metadata API. This includes factories for implementations of the + * primary interfaces (Metadata etc.) as well as utility classes and abstract base implementations. + *

+ * Release Notes + *

+ * Package Version 4.4.1, 2020-03-11 + *

+ * 1. Fixed a bug which caused SortedMap and SortedSet implementations to be serialized as normal + * Maps and Sets, respectively. This led to spurious exceptions when they were deserialized. + *

+ * Package Version 4.4, 2020-03-05 + *

+ * 1. Support storing/retrieving Class objects. + *

+ * 2. Use LinkedHashSet and LinkedHashMap to do a better job of preserving order. + *

+ * Package Version 4.3, 2019-11-15 + *

+ * Bug fix: if InstanceGetter had a custom serializer/deserializer for a type that implemented Map, + * Set or List, two bugs would cause an object of that type to be serialized as a Map, Set or List, + * bypassing the custom serializer. + *

+ * Package Version 4.2, 2019-10-02 + *

+ * Bug fix: when serializing Iterables and Maps, under some circumstances, objects that should have + * been recognized as having different types were being treated as if they were stored the same way. + * This resulted in errors when trying to deserialize metadata files that had been written by the + * buggy code. + *

+ * Package Version 4.1, 2019-04-10 + *

+ * InstanceGetter: Add support for matching abstract types and interfaces when encoding/decoding + * objects as proxy metadata. + *

+ * Package Version 4.0, 2019-03-25 + *

+ * 1. Accept, serialize and deserialize Class objects. + *

+ * 2. Create directories as needed to serialize the file in question. + *

+ */ +package edu.jhuapl.ses.jsqrl;