diff options
author | cvs2git | 2009-05-21 05:10:01 +0000 |
---|---|---|
committer | cvs2git | 2009-05-21 05:10:01 +0000 |
commit | 718ec483ef1166b496fb1ee510121e01d37f2f45 (patch) | |
tree | 3926d1e7e56c88711bb8f4c560e5f536f2e99091 | |
parent | 943cfc79210399d0b537b9330a7cc6136de85cc2 (diff) | |
download | org.eclipse.mylyn.tasks-718ec483ef1166b496fb1ee510121e01d37f2f45.tar.gz org.eclipse.mylyn.tasks-718ec483ef1166b496fb1ee510121e01d37f2f45.tar.xz org.eclipse.mylyn.tasks-718ec483ef1166b496fb1ee510121e01d37f2f45.zip |
This commit was manufactured by cvs2svn to create branch 'e_3_3_m_3_x'.
Cherrypick from master 2009-05-21 05:09:59 UTC relves 'NEW - bug 237042: [e3.4] use secure storage to save passwords':
org.eclipse.mylyn.tasks.core/.settings/org.eclipse.jdt.core.prefs
org.eclipse.mylyn.tasks.core/.settings/org.eclipse.jdt.ui.prefs
org.eclipse.mylyn.tasks.core/META-INF/MANIFEST.MF
org.eclipse.mylyn.tasks.core/about.html
org.eclipse.mylyn.tasks.core/build.properties
org.eclipse.mylyn.tasks.core/plugin.properties
org.eclipse.mylyn.tasks.core/plugin.xml
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/provisional/tasks/core/RepositoryClientManager.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/provisional/tasks/core/TasksUtil.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractSearchHandler.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTask.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTaskCategory.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTaskContainer.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AttributeMap.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AutomaticRepositoryTaskContainer.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/CommentQuoter.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DateRange.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DayDateRange.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DefaultTaskMapping.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/IRepositoryConstants.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/IRepositoryModelListener.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskJobFactory.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskList.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskListChangeListener.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskListRunnable.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskRepositoryElement.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskRepositoryFilter.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITasksCoreConstants.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITransferList.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/LocalRepositoryConnector.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/LocalTask.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/Messages.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/Person.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryExternalizationParticipant.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryModel.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryPerson.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryQuery.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryTaskHandleUtil.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryTemplateManager.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/SaxRepositoriesContentHandler.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/SaxRepositoriesWriter.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ScheduledTaskContainer.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivationHistory.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivityManager.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivityUtil.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskAttachment.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskCategory.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskComment.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskContainerDelta.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskExternalizationException.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskGroup.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskList.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoriesExternalizer.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryAdapter.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryLocation.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryManager.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskTask.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TransferList.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UncategorizedTaskContainer.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UnmatchedTaskContainer.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UnsubmittedTaskContainer.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/WeekDateRange.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ElementHandler.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/FileTaskAttachmentSource.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ITaskDataConstants.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ITaskDataManagerListener.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataExternalizer.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataManager.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataManagerEvent.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataState.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStateReader.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStateWriter.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStore.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TextTaskAttachmentSource.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/AbstractExternalizationParticipant.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/DelegatingTaskExternalizer.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/ExternalizationManager.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/IExternalizationContext.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/IExternalizationParticipant.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/Messages.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/TaskListExternalizationParticipant.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/TaskListExternalizer.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/messages.properties
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/messages.properties
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/Messages.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SubmitTaskAttachmentJob.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SubmitTaskJob.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizationSession.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeQueriesJob.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeRepositoriesJob.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeTasksJob.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/messages.properties
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractDuplicateDetector.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractRepositoryConnector.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractTaskListMigrator.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IAttributeContainer.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryElement.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryListener.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryManager.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryModel.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryPerson.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryQuery.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITask.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivationListener.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivityListener.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivityManager.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskAttachment.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskComment.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskContainer.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskMapping.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryResponse.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryStatus.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryTemplate.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskActivationAdapter.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskActivityAdapter.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskMapping.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskRepository.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskRepositoryLocationFactory.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskAttachmentHandler.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskAttachmentSource.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskDataHandler.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/ITaskDataManager.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/ITaskDataWorkingCopy.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentMapper.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentModel.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentPartSource.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttribute.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttributeMapper.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttributeMetaData.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskCommentMapper.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskData.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataCollector.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModel.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModelEvent.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModelListener.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskMapper.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskOperation.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskRelation.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/ISynchronizationSession.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJob.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJobEvent.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJobListener.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SynchronizationJob.java
org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/TaskJob.java
144 files changed, 19311 insertions, 0 deletions
diff --git a/org.eclipse.mylyn.tasks.core/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.mylyn.tasks.core/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..fbac23913 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,342 @@ +#Tue May 12 20:42:44 PDT 2009 +eclipse.preferences.version=1 +org.eclipse.jdt.core.codeComplete.argumentPrefixes= +org.eclipse.jdt.core.codeComplete.argumentSuffixes= +org.eclipse.jdt.core.codeComplete.fieldPrefixes= +org.eclipse.jdt.core.codeComplete.fieldSuffixes= +org.eclipse.jdt.core.codeComplete.localPrefixes= +org.eclipse.jdt.core.codeComplete.localSuffixes= +org.eclipse.jdt.core.codeComplete.staticFieldPrefixes= +org.eclipse.jdt.core.codeComplete.staticFieldSuffixes= +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.5 +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.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=enabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=warning +org.eclipse.jdt.core.compiler.problem.nullReference=error +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore +org.eclipse.jdt.core.compiler.problem.potentialNullReference=warning +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.5 +org.eclipse.jdt.core.compiler.taskCaseSensitive=enabled +org.eclipse.jdt.core.compiler.taskPriorities=NORMAL,HIGH,NORMAL +org.eclipse.jdt.core.compiler.taskTags=TODO,FIXME,XXX +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=80 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=1 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=0 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines=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=true +org.eclipse.jdt.core.formatter.comment.format_block_comments=false +org.eclipse.jdt.core.formatter.comment.format_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=false +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.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert +org.eclipse.jdt.core.formatter.comment.line_length=120 +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=2 +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=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_member=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_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not 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=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=true +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.lineSplit=120 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=true +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=true +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=tab +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true diff --git a/org.eclipse.mylyn.tasks.core/.settings/org.eclipse.jdt.ui.prefs b/org.eclipse.mylyn.tasks.core/.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 000000000..766f9cb69 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,63 @@ +#Thu Sep 11 16:27:18 PDT 2008 +cleanup_settings_version=2 +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +formatter_profile=_Mylyn based on Eclipse +formatter_settings_version=11 +internal.default.compliance=default +org.eclipse.jdt.ui.exception.name=e +org.eclipse.jdt.ui.gettersetter.use.is=true +org.eclipse.jdt.ui.javadoc=false +org.eclipse.jdt.ui.keywordthis=false +org.eclipse.jdt.ui.overrideannotation=true +org.eclipse.jdt.ui.text.custom_code_templates=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?><templates><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\r\n * @return the ${bare_field_name}\r\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter method" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\r\n * @param ${param} the ${bare_field_name} to set\r\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="filecomment_context" deleted\="false" description\="Comment for created Java files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.filecomment" name\="filecomment">/**\r\n * \r\n */</template><template autoinsert\="false" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\r\n * @author ${user}\r\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for fields" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\r\n * \r\n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="false" context\="overridecomment_context" deleted\="false" description\="Comment for overriding methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.overridecomment" name\="overridecomment"/><template autoinsert\="false" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.newtype" name\="newtype">/*******************************************************************************\r\n * Copyright (c) 2004, 2008 Tasktop Technologies and others.\r\n * All rights reserved. This program and the accompanying materials\r\n * are made available under the terms of the Eclipse Public License v1.0\r\n * which accompanies this distribution, and is available at\r\n * http\://www.eclipse.org/legal/epl-v10.html\r\n *\r\n * Contributors\:\r\n * Tasktop Technologies - initial API and implementation\r\n *******************************************************************************/\r\n\r\n${package_declaration}\r\n\r\n${typecomment}\r\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.classbody" name\="classbody">\r\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.interfacebody" name\="interfacebody">\r\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.enumbody" name\="enumbody">\r\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.annotationbody" name\="annotationbody">\r\n</template><template autoinsert\="false" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\r\n${exception_var}.printStackTrace();</template><template autoinsert\="false" context\="methodbody_context" deleted\="false" description\="Code in created method stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.methodbody" name\="methodbody">// ignore\r\n${body_statement}</template><template autoinsert\="false" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\r\n// ignore</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate methods" enabled\="true" id\="org.eclipse.jdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\r\n * ${tags}\r\n * ${see_to_target}\r\n */</template><template autoinsert\="true" context\="gettercomment_context" deleted\="false" description\="Comment for getter function" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.gettercomment" name\="gettercomment">/**\r\n * @return the ${bare_field_name}\r\n */</template><template autoinsert\="true" context\="settercomment_context" deleted\="false" description\="Comment for setter function" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.settercomment" name\="settercomment">/**\r\n * @param ${param} the ${bare_field_name} to set\r\n */</template><template autoinsert\="true" context\="constructorcomment_context" deleted\="false" description\="Comment for created constructors" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.constructorcomment" name\="constructorcomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="filecomment_context" deleted\="false" description\="Comment for created JavaScript files" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.filecomment" name\="filecomment">/**\r\n * \r\n */</template><template autoinsert\="true" context\="typecomment_context" deleted\="false" description\="Comment for created types" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.typecomment" name\="typecomment">/**\r\n * @author ${user}\r\n *\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="fieldcomment_context" deleted\="false" description\="Comment for vars" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.fieldcomment" name\="fieldcomment">/**\r\n * \r\n */</template><template autoinsert\="true" context\="methodcomment_context" deleted\="false" description\="Comment for non-overriding function" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.methodcomment" name\="methodcomment">/**\r\n * ${tags}\r\n */</template><template autoinsert\="true" context\="overridecomment_context" deleted\="false" description\="Comment for overriding functions" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.overridecomment" name\="overridecomment">/* (non-Jsdoc)\r\n * ${see_to_overridden}\r\n */</template><template autoinsert\="true" context\="delegatecomment_context" deleted\="false" description\="Comment for delegate functions" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.delegatecomment" name\="delegatecomment">/**\r\n * ${tags}\r\n * ${see_to_target}\r\n */</template><template autoinsert\="true" context\="newtype_context" deleted\="false" description\="Newly created files" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.newtype" name\="newtype">${filecomment}\r\n${package_declaration}\r\n\r\n${typecomment}\r\n${type_declaration}</template><template autoinsert\="true" context\="classbody_context" deleted\="false" description\="Code in new class type bodies" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.classbody" name\="classbody">\r\n</template><template autoinsert\="true" context\="interfacebody_context" deleted\="false" description\="Code in new interface type bodies" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.interfacebody" name\="interfacebody">\r\n</template><template autoinsert\="true" context\="enumbody_context" deleted\="false" description\="Code in new enum type bodies" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.enumbody" name\="enumbody">\r\n</template><template autoinsert\="true" context\="annotationbody_context" deleted\="false" description\="Code in new annotation type bodies" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.annotationbody" name\="annotationbody">\r\n</template><template autoinsert\="true" context\="catchblock_context" deleted\="false" description\="Code in new catch blocks" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.catchblock" name\="catchblock">// ${todo} Auto-generated catch block\r\n${exception_var}.printStackTrace();</template><template autoinsert\="true" context\="methodbody_context" deleted\="false" description\="Code in created function stubs" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.methodbody" name\="methodbody">// ${todo} Auto-generated function stub\r\n${body_statement}</template><template autoinsert\="true" context\="constructorbody_context" deleted\="false" description\="Code in created constructor stubs" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.constructorbody" name\="constructorbody">${body_statement}\r\n// ${todo} Auto-generated constructor stub</template><template autoinsert\="true" context\="getterbody_context" deleted\="false" description\="Code in created getters" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.getterbody" name\="getterbody">return ${field};</template><template autoinsert\="true" context\="setterbody_context" deleted\="false" description\="Code in created setters" enabled\="true" id\="org.eclipse.wst.jsdt.ui.text.codetemplates.setterbody" name\="setterbody">${field} \= ${param};</template></templates> +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_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=false +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_to_enhanced_for_loop=true +sp_cleanup.correct_indentation=true +sp_cleanup.format_source_code=true +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_variable_declarations_final=true +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=true +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=true +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=true +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=true +sp_cleanup.remove_unnecessary_nls_tags=true +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_blocks=true +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/org.eclipse.mylyn.tasks.core/META-INF/MANIFEST.MF b/org.eclipse.mylyn.tasks.core/META-INF/MANIFEST.MF new file mode 100644 index 000000000..a5c7c24cd --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/META-INF/MANIFEST.MF @@ -0,0 +1,20 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: %Bundle-Name +Bundle-SymbolicName: org.eclipse.mylyn.tasks.core;singleton:=true +Bundle-Version: 3.2.0.qualifier +Bundle-Vendor: %Bundle-Vendor +Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Require-Bundle: org.eclipse.core.runtime, + org.eclipse.core.net, + org.eclipse.mylyn.commons.core;bundle-version="[3.0.0,4.0.0)", + org.eclipse.mylyn.commons.net;bundle-version="[3.0.0,4.0.0)" +Export-Package: org.eclipse.mylyn.internal.provisional.tasks.core;x-internal:=true, + org.eclipse.mylyn.internal.tasks.core;x-friends:="org.eclipse.mylyn.tasks.ui,org.eclipse.mylyn.tasks.bugs", + org.eclipse.mylyn.internal.tasks.core.data;x-friends:="org.eclipse.mylyn.tasks.ui,org.eclipse.mylyn.tasks.bugs", + org.eclipse.mylyn.internal.tasks.core.externalization;x-friends:="org.eclipse.mylyn.tasks.ui,org.eclipse.mylyn.tasks.bugs", + org.eclipse.mylyn.internal.tasks.core.sync;x-friends:="org.eclipse.mylyn.tasks.ui,org.eclipse.mylyn.tasks.bugs", + org.eclipse.mylyn.tasks.core, + org.eclipse.mylyn.tasks.core.data, + org.eclipse.mylyn.tasks.core.sync +Bundle-Localization: plugin diff --git a/org.eclipse.mylyn.tasks.core/about.html b/org.eclipse.mylyn.tasks.core/about.html new file mode 100644 index 000000000..d774b07c7 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/about.html @@ -0,0 +1,27 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"> +<html> +<head> +<title>About</title> +<meta http-equiv=Content-Type content="text/html; charset=ISO-8859-1"> +</head> +<body lang="EN-US"> +<h2>About This Content</h2> + +<p>June 25, 2008</p> +<h3>License</h3> + +<p>The Eclipse Foundation makes available all content in this plug-in ("Content"). Unless otherwise +indicated below, the Content is provided to you under the terms and conditions of the +Eclipse Public License Version 1.0 ("EPL"). A copy of the EPL is available +at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>. +For purposes of the EPL, "Program" will mean the Content.</p> + +<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is +being redistributed by another party ("Redistributor") and different terms and conditions may +apply to your use of any object code in the Content. Check the Redistributor's license that was +provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise +indicated below, the terms and conditions of the EPL still apply to any source code in the Content +and such source code may be obtained at <a href="/">http://www.eclipse.org</a>.</p> + +</body> +</html>
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/build.properties b/org.eclipse.mylyn.tasks.core/build.properties new file mode 100644 index 000000000..36dd8a8a4 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/build.properties @@ -0,0 +1,16 @@ +############################################################################### +# Copyright (c) 2005, 2006 Mylyn project committers and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +############################################################################### +bin.includes = META-INF/,\ + plugin.xml,\ + .,\ + about.html,\ + plugin.properties +src.includes = about.html,\ + schema/ +jre.compilation.profile = J2SE-1.5 +source.. = src/ diff --git a/org.eclipse.mylyn.tasks.core/plugin.properties b/org.eclipse.mylyn.tasks.core/plugin.properties new file mode 100644 index 000000000..536aadb84 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/plugin.properties @@ -0,0 +1,3 @@ +#Properties file for org.eclipse.mylyn.tasks.core +Bundle-Vendor = Eclipse Mylyn +Bundle-Name = Mylyn Tasks Core diff --git a/org.eclipse.mylyn.tasks.core/plugin.xml b/org.eclipse.mylyn.tasks.core/plugin.xml new file mode 100644 index 000000000..e218e14c8 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/plugin.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<?eclipse version="3.0"?> +<plugin> + <extension-point id="templates" name="templates" schema="schema/templates.exsd"/> + + <!-- + <extension + point="org.eclipse.ui.workbench.texteditor.hyperlinkDetectors"> + <hyperlinkDetector class="org.eclipse.mylyn.internal.bugs.java.BugzillaHyperLinkDetector"/> + </extension> + --> + +<!-- + <extension + point="org.eclipse.mylyn.core.context"> + <structureBridge + activeSearchIcon="icons/elcl16/edge-ref-bug.gif" + activeSearchLabel="Bugzilla References" + class="org.eclipse.mylyn.bugs.BugzillaStructureBridge" + name="Bugzilla Structure Bridge"/> + </extension> + + <extension + point="org.eclipse.mylyn.ui.context"> + <uiBridge + class="org.eclipse.mylyn.bugs.BugzillaUiBridge" + contentType="bugzilla"/> + </extension> +--> +</plugin> diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/provisional/tasks/core/RepositoryClientManager.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/provisional/tasks/core/RepositoryClientManager.java new file mode 100644 index 000000000..d3064a62c --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/provisional/tasks/core/RepositoryClientManager.java @@ -0,0 +1,166 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylyn.internal.provisional.tasks.core; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.tasks.core.IRepositoryListener; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.TaskRepositoryLocationFactory; + +/** + * TODO: fix class loading problems caused by serialization and make API + * + * @author Steffen Pingel + */ +abstract class RepositoryClientManager<T, C extends Serializable> implements IRepositoryListener { + + private final Map<String, T> clientByUrl = new HashMap<String, T>(); + + private final Map<String, C> clientDataByUrl = new HashMap<String, C>(); + + private final File cacheFile; + + private TaskRepositoryLocationFactory taskRepositoryLocationFactory; + + public RepositoryClientManager(File cacheFile) { + Assert.isNotNull(cacheFile); + this.cacheFile = cacheFile; + readCache(); + } + + public synchronized T getClient(TaskRepository taskRepository) { + Assert.isNotNull(taskRepository); + T client = clientByUrl.get(taskRepository.getRepositoryUrl()); + if (client == null) { + C data = clientDataByUrl.get(taskRepository.getRepositoryUrl()); + if (data == null) { + data = createRepositoryConfiguration(); + clientDataByUrl.put(taskRepository.getRepositoryUrl(), data); + } + + client = createClient(taskRepository, data); + clientByUrl.put(taskRepository.getRepositoryUrl(), client); + } + return client; + } + + protected abstract C createRepositoryConfiguration(); + + protected abstract T createClient(TaskRepository taskRepository, C data); + + public void repositoriesRead() { + // ignore + } + + public synchronized void repositoryAdded(TaskRepository repository) { + removeClient(repository); + clientDataByUrl.remove(repository.getRepositoryUrl()); + } + + private void removeClient(TaskRepository repository) { + clientByUrl.remove(repository.getRepositoryUrl()); + } + + public synchronized void repositoryRemoved(TaskRepository repository) { + removeClient(repository); + clientDataByUrl.remove(repository.getRepositoryUrl()); + } + + public synchronized void repositorySettingsChanged(TaskRepository repository) { + removeClient(repository); + } + + @SuppressWarnings("unchecked") + public void readCache() { + if (cacheFile == null || !cacheFile.exists()) { + return; + } + + ObjectInputStream in = null; + try { + in = new ObjectInputStream(new FileInputStream(cacheFile)); + int size = in.readInt(); + for (int i = 0; i < size; i++) { + String url = (String) in.readObject(); + C data = (C) in.readObject(); + if (url != null && data != null) { + clientDataByUrl.put(url, data); + } + } + } catch (Throwable e) { + StatusHandler.log(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, + "The respository configuration cache could not be read", e)); //$NON-NLS-1$ + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + // ignore + } + } + } + + } + + public void writeCache() { + if (cacheFile == null) { + return; + } + + ObjectOutputStream out = null; + try { + out = new ObjectOutputStream(new FileOutputStream(cacheFile)); + out.writeInt(clientDataByUrl.size()); + for (String url : clientDataByUrl.keySet()) { + out.writeObject(url); + out.writeObject(clientDataByUrl.get(url)); + } + } catch (IOException e) { + StatusHandler.log(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, + "The respository configuration cache could not be written", e)); //$NON-NLS-1$ + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // ignore + } + } + } + } + + public TaskRepositoryLocationFactory getTaskRepositoryLocationFactory() { + return taskRepositoryLocationFactory; + } + + public void setTaskRepositoryLocationFactory(TaskRepositoryLocationFactory taskRepositoryLocationFactory) { + this.taskRepositoryLocationFactory = taskRepositoryLocationFactory; + } + + public void repositoryUrlChanged(TaskRepository repository, String oldUrl) { + // ignore + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/provisional/tasks/core/TasksUtil.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/provisional/tasks/core/TasksUtil.java new file mode 100644 index 000000000..b70be4aef --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/provisional/tasks/core/TasksUtil.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.provisional.tasks.core; + +/** + * @author Steffen Pingel + */ +public class TasksUtil { + + public static String decode(String text) { + boolean escaped = false; + StringBuffer sb = new StringBuffer(text.length()); + StringBuffer escapedText = new StringBuffer(4); + char[] chars = text.toCharArray(); + for (char c : chars) { + if (c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '.') { + if (escaped) { + escapedText.append(c); + } else { + sb.append(c); + } + } else if (c == '%') { + if (escaped) { + throw new IllegalArgumentException("Unexpected '%' sign in '" + text + "'"); //$NON-NLS-1$ //$NON-NLS-2$ + } + escaped = !escaped; + } else if (c == '_') { + if (!escaped) { + throw new IllegalArgumentException("Unexpected '_' sign in '" + text + "'"); //$NON-NLS-1$ //$NON-NLS-2$ + } + try { + sb.append((char) Integer.parseInt(escapedText.toString(), 16)); + escapedText.setLength(0); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid escape code in '" + text + "'"); //$NON-NLS-1$ //$NON-NLS-2$ + } + escaped = !escaped; + } else { + throw new IllegalArgumentException("Unexpected character '" + c + "' in '" + text + "'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + } + return sb.toString(); + } + + public static String encode(String text) { + StringBuffer sb = new StringBuffer(text.length()); + char[] chars = text.toCharArray(); + for (char c : chars) { + if (c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '.') { + sb.append(c); + } else { + sb.append("%" + Integer.toHexString(c).toUpperCase() + "_"); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + return sb.toString(); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractSearchHandler.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractSearchHandler.java new file mode 100644 index 000000000..576a10e50 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractSearchHandler.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import org.eclipse.mylyn.tasks.core.IRepositoryQuery; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.TaskData; + +/** + * Note: This is provisional API that is used by connectors. + * <p> + * DO NOT CHANGE. + * + * @author Steffen Pingel + */ +public abstract class AbstractSearchHandler { + + public abstract String getConnectorKind(); + + public abstract boolean queryForText(TaskRepository taskRepository, IRepositoryQuery query, TaskData taskData, + String searchString); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTask.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTask.java new file mode 100644 index 000000000..235dde3cd --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTask.java @@ -0,0 +1,513 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.mylyn.tasks.core.IRepositoryElement; +import org.eclipse.mylyn.tasks.core.ITask; + +/** + * Encapsulates tasks that reside on a repository or local computer and participate in synchronization with the source + * that contains their data. + * + * @author Mik Kersten + * @author Rob Elves + * @since 2.0 + */ +public abstract class AbstractTask extends AbstractTaskContainer implements ITask, ITaskRepositoryElement { + + public static final String DEFAULT_TASK_KIND = "task"; //$NON-NLS-1$ + + private String repositoryUrl; + + private String taskKind = DEFAULT_TASK_KIND; + + private final String taskId; + + private String owner; + + private boolean active = false; + + private String summary; + + private String priority = PriorityLevel.getDefault().toString(); + + private boolean isNotifiedIncoming = false; + + private boolean reminded = false; + + private final Set<AbstractTaskContainer> containers = new HashSet<AbstractTaskContainer>(); + + // ************ Synch **************** + + /** The last time this task's bug report was in a synchronized (read?) state. */ + private String lastReadTimeStamp; + + private boolean synchronizing; + + private boolean submitting; + + private SynchronizationState synchronizationState = SynchronizationState.SYNCHRONIZED; + + // transient + private IStatus status = null; + + private boolean stale = false; + + private Date completionDate = null; + + private Date creationDate = null; + + private Date modificationDate = null; + + private DateRange scheduledForDate = null; + + private Date dueDate = null; + + private String notes = ""; //$NON-NLS-1$ + + private int estimatedTimeHours = 1; + + private boolean markReadPending; + + // TODO 4.0 make private + protected String taskKey; + + private AttributeMap attributeMap; + + private boolean changed; + + public AbstractTask(String repositoryUrl, String taskId, String summary) { + super(RepositoryTaskHandleUtil.getHandle(repositoryUrl, taskId)); + this.repositoryUrl = repositoryUrl; + this.taskId = taskId; + this.summary = summary; + } + + /** + * Final to preserve the handle identifier format required by the framework. + */ + @Override + public final String getHandleIdentifier() { + return super.getHandleIdentifier(); + } + + /** + * True for tasks that can be modified without a round-trip to a server. For example, such a task can be marked + * completed via the Task List. + * + * @deprecated use <code>task instanceof LocalTask</code> instead + */ + @Deprecated + public abstract boolean isLocal(); + + public abstract String getConnectorKind(); + + @Deprecated + public String getLastReadTimeStamp() { + return lastReadTimeStamp; + } + + @Deprecated + public void setLastReadTimeStamp(String lastReadTimeStamp) { + this.lastReadTimeStamp = lastReadTimeStamp; + } + + /** + * @since 3.0 + */ + public void setSynchronizationState(SynchronizationState syncState) { + Assert.isNotNull(syncState); + this.synchronizationState = syncState; + } + + /** + * @since 3.0 + */ + public SynchronizationState getSynchronizationState() { + return synchronizationState; + } + + public boolean isSynchronizing() { + return synchronizing; + } + + public void setSynchronizing(boolean sychronizing) { + this.synchronizing = sychronizing; + } + + public boolean isNotified() { + return isNotifiedIncoming; + } + + public void setNotified(boolean notified) { + isNotifiedIncoming = notified; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + if (!areEqual(this.owner, owner)) { + String oldValue = this.owner; + this.owner = owner; + firePropertyChange("owner", oldValue, owner); //$NON-NLS-1$ + } + } + + /** + * Return the status, such as an error or warning, associated with this task. + */ + public IStatus getStatus() { + return status; + } + + public void setStatus(IStatus status) { + this.status = status; + } + + public final String getTaskId() { + return taskId; + } + + public final String getRepositoryUrl() { + return repositoryUrl; + } + + @Override + public final void setHandleIdentifier(String handleIdentifier) { + throw new RuntimeException("Cannot set the handle identifier of a task, set repository URL instead."); //$NON-NLS-1$ + } + + public final void setRepositoryUrl(String repositoryUrl) { + this.repositoryUrl = repositoryUrl; + super.setHandleIdentifier(RepositoryTaskHandleUtil.getHandle(repositoryUrl, taskId)); + } + + /** + * User identifiable key for the task to be used in UI facilities such as label displays and hyperlinked references. + * Can return the same as the ID (e.g. in the case of Bugzilla). Can return null if no such label exists. + */ + public String getTaskKey() { + return (taskKey == null) ? taskId : taskKey; + } + + @Deprecated + public boolean isSubmitting() { + return submitting; + } + + @Deprecated + public void setSubmitting(boolean submitting) { + this.submitting = submitting; + } + + @Override + public String toString() { + return summary; + } + + public void setActive(boolean active) { + this.active = active; + } + + public boolean isActive() { + return active; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof AbstractTask) { + return this.getHandleIdentifier().equals(((ITask) obj).getHandleIdentifier()); + } else { + return false; + } + } + + @Override + public int hashCode() { + return taskId.hashCode(); + } + + public boolean isCompleted() { + return completionDate != null; + } + + /** + * @deprecated use setCompletionDate() + */ + @Deprecated + public void setCompleted(boolean completed) { + if (completed) { + completionDate = new Date(); + } else { + completionDate = null; + } + } + + @Override + public String getPriority() { + return priority; + } + + public void setPriority(String priority) { + if (!areEqual(this.priority, priority)) { + String oldValue = this.priority; + this.priority = priority; + firePropertyChange("priority", oldValue, priority); //$NON-NLS-1$ + } + } + + public String getNotes() { + // TODO: removed check for null once xml updated. + if (notes == null) { + notes = ""; //$NON-NLS-1$ + } + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + /** + * @deprecated Use {@link #getEstimatedTimeHours()} instead + */ + @Deprecated + public int getEstimateTimeHours() { + return getEstimatedTimeHours(); + } + + public int getEstimatedTimeHours() { + return estimatedTimeHours; + } + + public void setEstimatedTimeHours(int estimated) { + this.estimatedTimeHours = estimated; + } + + void addParentContainer(AbstractTaskContainer container) { + containers.add(container); + } + + void removeParentContainer(AbstractTaskContainer container) { + containers.remove(container); + } + + public Set<AbstractTaskContainer> getParentContainers() { + return new HashSet<AbstractTaskContainer>(containers); + } + + @Override + public String getSummary() { + return summary; + } + + public Date getCompletionDate() { + return completionDate; + } + + public void setScheduledForDate(DateRange reminderDate) { + scheduledForDate = reminderDate; + } + + public DateRange getScheduledForDate() { + return scheduledForDate; + } + + public boolean isReminded() { + return reminded; + } + + public void setReminded(boolean reminded) { + this.reminded = reminded; + } + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + if (!areEqual(this.creationDate, creationDate)) { + Date oldValue = this.creationDate; + this.creationDate = creationDate; + firePropertyChange("creationDate", oldValue, creationDate); //$NON-NLS-1$ + } + } + + public void setSummary(String summary) { + Assert.isNotNull(summary); + if (!areEqual(this.summary, summary)) { + String oldValue = this.summary; + this.summary = summary; + firePropertyChange("summary", oldValue, summary); //$NON-NLS-1$ + } + } + + public void setCompletionDate(Date completionDate) { + if (!areEqual(this.completionDate, completionDate)) { + Date oldValue = this.completionDate; + this.completionDate = completionDate; + firePropertyChange("completionDate", oldValue, completionDate); //$NON-NLS-1$ + } + } + + private boolean areEqual(Object oldValue, Object newValue) { + return (oldValue != null) ? oldValue.equals(newValue) : oldValue == newValue; + } + + private void firePropertyChange(String key, Object oldValue, Object newValue) { +// PropertyChangeEvent event = new PropertyChangeEvent(this, key, oldValue, newValue); +// for (PropertyChangeListener listener : propertyChangeListeners) { +// listener.propertyChange(event); +// } + changed = true; + } + + public boolean isChanged() { + return changed; + } + + public void setChanged(boolean changed) { + this.changed = changed; + } + + /** + * @deprecated use {@link TaskActivityManager#isPastReminder(AbstractTask)} instead + */ + @Deprecated + public boolean isPastReminder() { + if (isCompleted() || scheduledForDate == null || !(getScheduledForDate() instanceof DayDateRange)) { + return false; + } else { + if (/*!internalIsFloatingScheduledDate() && */scheduledForDate.getEndDate().compareTo( + TaskActivityUtil.getCalendar()) < 0) { + return true; + } else { + return false; + } + } + } + + public String getTaskKind() { + return taskKind; + } + + public void setTaskKind(String taskKind) { + if (!areEqual(this.taskKind, taskKind)) { + String oldValue = this.taskKind; + this.taskKind = taskKind; + firePropertyChange("taskKind", oldValue, taskKind); //$NON-NLS-1$ + } + } + + @Override + public int compareTo(IRepositoryElement taskListElement) { + return summary.compareTo(((AbstractTask) taskListElement).summary); + } + + public Date getDueDate() { + return dueDate; + } + + public void setDueDate(Date date) { + if (!areEqual(this.dueDate, date)) { + Date oldValue = this.dueDate; + this.dueDate = date; + firePropertyChange("dueDate", oldValue, date); //$NON-NLS-1$ + } + } + + @Deprecated + public boolean isStale() { + return stale; + } + + @Deprecated + public void setStale(boolean stale) { + this.stale = stale; + } + + public Date getModificationDate() { + return modificationDate; + } + + public void setModificationDate(Date modificationDate) { + if (!areEqual(this.modificationDate, modificationDate)) { + Date oldValue = this.modificationDate; + this.modificationDate = modificationDate; + firePropertyChange("modificationDate", oldValue, modificationDate); //$NON-NLS-1$ + } + } + + public boolean isMarkReadPending() { + return markReadPending; + } + + public void setMarkReadPending(boolean markReadPending) { + this.markReadPending = markReadPending; + } + + public void setTaskKey(String taskKey) { + if (!areEqual(this.taskKey, taskKey)) { + String oldValue = this.taskKey; + this.taskKey = taskKey; + firePropertyChange("taskKey", oldValue, taskKey); //$NON-NLS-1$ + } + } + + public synchronized String getAttribute(String key) { + return (attributeMap != null) ? attributeMap.getAttribute(key) : null; + } + + public synchronized Map<String, String> getAttributes() { + if (attributeMap != null) { + return attributeMap.getAttributes(); + } else { + return Collections.emptyMap(); + } + } + + public void setAttribute(String key, String value) { + String oldValue; + synchronized (this) { + if (attributeMap == null) { + attributeMap = new AttributeMap(); + } + oldValue = attributeMap.getAttribute(key); + if (!areEqual(oldValue, value)) { + attributeMap.setAttribute(key, value); + } else { + return; + } + } + firePropertyChange(key, oldValue, value); + } + + @Override + public void setUrl(String url) { + String oldValue = getUrl(); + if (!areEqual(oldValue, url)) { + super.setUrl(url); + firePropertyChange("url", oldValue, url); //$NON-NLS-1$ + } + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTaskCategory.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTaskCategory.java new file mode 100644 index 000000000..cade40603 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTaskCategory.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +/** + * A container that stores tasks from any repository. A task can only have a single AbstractTaskCategory parent (only be + * in one category at a time). + * + * @author Mik Kersten + */ +public abstract class AbstractTaskCategory extends AbstractTaskContainer { + + public AbstractTaskCategory(String handleAndDescription) { + super(handleAndDescription); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTaskContainer.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTaskContainer.java new file mode 100644 index 000000000..781a8bfef --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTaskContainer.java @@ -0,0 +1,183 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.PlatformObject; +import org.eclipse.mylyn.tasks.core.IRepositoryElement; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITaskContainer; +import org.eclipse.mylyn.tasks.core.ITask.PriorityLevel; + +/** + * Top-level Task List element that can contain other Task List elements. + * + * @author Mik Kersten + */ +public abstract class AbstractTaskContainer extends PlatformObject implements IRepositoryElement, ITaskContainer { + + private String handleIdentifier = ""; //$NON-NLS-1$ + + private final Collection<ITask> children = new CopyOnWriteArrayList<ITask>(); + + /** + * Optional URL corresponding to the web resource associated with this container. + */ + private String url; + + public AbstractTaskContainer(String handleAndDescription) { + Assert.isNotNull(handleAndDescription); + this.handleIdentifier = handleAndDescription; + } + + /** + * Use {@link TaskList} methods instead. + */ + public void internalAddChild(AbstractTask task) { + Assert.isNotNull(task); + children.add(task); + } + + /** + * Use {@link TaskList} methods instead. + * + * @return + * @since 3.0 + */ + public boolean internalRemoveChild(ITask task) { + return children.remove(task); + } + + /** + * Removes any cyclic dependencies in children. + * + * TODO: review to make sure that this is too expensive, or move to creation. + * + * @since 3.0 + */ + public Collection<ITask> getChildren() { + return Collections.unmodifiableCollection(children); + } + + /** + * Maxes out at a depth of 10. + * + * TODO: review policy + */ + public boolean contains(String handle) { + Assert.isNotNull(handle); + return containsHelper(children, handle, new HashSet<IRepositoryElement>()); + } + + private boolean containsHelper(Collection<ITask> children, String handle, Set<IRepositoryElement> visitedContainers) { + for (ITask child : children) { + if (visitedContainers.contains(child)) { + continue; + } + visitedContainers.add(child); + if (child instanceof ITaskContainer) { + if (handle.equals(child.getHandleIdentifier()) + || containsHelper(((ITaskContainer) child).getChildren(), handle, visitedContainers)) { + return true; + } + } + } + return false; + } + + public String getSummary() { + return handleIdentifier; + } + + public boolean isEmpty() { + return children.isEmpty(); + } + + public String getHandleIdentifier() { + return handleIdentifier; + } + + public void setHandleIdentifier(String handleIdentifier) { + this.handleIdentifier = handleIdentifier; + } + + @Override + public int hashCode() { + return handleIdentifier.hashCode(); + } + + public void setUrl(String url) { + this.url = url; + } + + /** + * @return can be null + */ + public String getUrl() { + return url; + } + + @Override + public boolean equals(Object object) { + if (object == null) { + return false; + } + if (object instanceof AbstractTaskContainer) { + IRepositoryElement compare = (IRepositoryElement) object; + return this.getHandleIdentifier().equals(compare.getHandleIdentifier()); + } else { + return false; + } + } + + @Override + public String toString() { + return "container: " + handleIdentifier; //$NON-NLS-1$ + } + + public String getPriority() { + String highestPriority = PriorityLevel.P5.toString(); + Collection<ITask> tasks = getChildren(); + if (tasks.isEmpty()) { + return PriorityLevel.P1.toString(); + } + for (ITask task : tasks) { + if (highestPriority.compareTo(task.getPriority()) > 0) { + highestPriority = task.getPriority(); + } + } + return highestPriority; + } + + /** + * The handle for most containers is their summary. Override to specify a different natural ordering. + */ + public int compareTo(IRepositoryElement taskListElement) { + return getHandleIdentifier().compareTo(taskListElement.getHandleIdentifier()); + } + + /** + * If false, user is unable to manipulate (i.e. rename/delete), no preferences are available. + * + * @since 2.3 + */ + public boolean isUserManaged() { + return true; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AttributeMap.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AttributeMap.java new file mode 100644 index 000000000..70b5888c4 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AttributeMap.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; + +/** + * @author Steffen Pingel + */ +public class AttributeMap { + + private final Map<String, String> attributes; + + public AttributeMap() { + attributes = new HashMap<String, String>(); + } + + public String getAttribute(String key) { + return attributes.get(key); + } + + public Map<String, String> getAttributes() { + return new HashMap<String, String>(attributes); + } + + public void setAttribute(String key, String value) { + Assert.isNotNull(key); + if (value == null) { + attributes.remove(key); + } else { + attributes.put(key, value); + } + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AutomaticRepositoryTaskContainer.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AutomaticRepositoryTaskContainer.java new file mode 100644 index 000000000..78db9ce13 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AutomaticRepositoryTaskContainer.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import org.eclipse.mylyn.tasks.core.ITask.PriorityLevel; + +/** + * @author Mik Kersten + */ +public abstract class AutomaticRepositoryTaskContainer extends AbstractTaskCategory implements ITaskRepositoryElement { + + protected String repositoryUrl; + + private final String connectorKind; + + private final String handleSuffix; + + public AutomaticRepositoryTaskContainer(String handleSuffix, String connectorKind, String repositoryUrl) { + super(repositoryUrl + "-" + handleSuffix); //$NON-NLS-1$ + this.handleSuffix = handleSuffix; + this.connectorKind = connectorKind; + this.repositoryUrl = repositoryUrl; + } + + @Override + public boolean isUserManaged() { + return false; + } + + public String getConnectorKind() { + return connectorKind; + } + + public String getRepositoryUrl() { + return repositoryUrl; + } + + @Override + public String getPriority() { + return PriorityLevel.P1.toString(); + } + + /** + * setting will also refactor handle + */ + public void setRepositoryUrl(String repositoryUrl) { + this.repositoryUrl = repositoryUrl; + setHandleIdentifier(repositoryUrl + "-" + handleSuffix); //$NON-NLS-1$ + } + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/CommentQuoter.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/CommentQuoter.java new file mode 100644 index 000000000..f0c2c0407 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/CommentQuoter.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Willian Mitsuda and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Willian Mitsuda - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +/** + * Utility class to handle many text quoting scenarios + * <p> + * Each line of text is quoted individually and wrapped, according to the {@link lineSize} attribute + * <p> + * The wrapping policy is the following: + * <p> + * <ol> + * <li>A substring of {@link lineSize} characters is extracted and examined + * <li>If the next character after the substring is a blank space, the substring is quoted + * <li>If don't, the substring is searched backwards for a blank space; if one is found, the substring until the blank + * space is quoted + * <li>If no blank space is found, the entire substring is quoted + * <li>The remaining of substring + line are reevaluated on step 1 + * </ol> + * + * @author Willian Mitsuda + */ +public class CommentQuoter { + + public static final int DEFAULT_WRAP_SIZE = 80; + + private final int lineSize; + + public CommentQuoter() { + this(DEFAULT_WRAP_SIZE); + } + + public CommentQuoter(int lineSize) { + this.lineSize = lineSize; + } + + /** + * Quote a text, wrapping if necessary + */ + public String quote(String text) { + StringBuilder sb = new StringBuilder(text.length() + text.length() / lineSize); + + String[] lines = text.split("\n"); //$NON-NLS-1$ + for (String line : lines) { + if (line.trim().equals("")) { //$NON-NLS-1$ + sb.append("> \n"); //$NON-NLS-1$ + continue; + } + + int pos = 0; + while (pos < line.length()) { + int wrapPos = pos + lineSize; + if (wrapPos < line.length()) { + // Try to find a space to wrap the line + while (wrapPos > pos) { + char wrapChar = line.charAt(wrapPos); + if (Character.isSpaceChar(wrapChar)) { + break; + } + wrapPos--; + } + if (wrapPos == pos) { + // There is no space; don't break the line and find the + // next space after the limit... + wrapPos = pos + lineSize; + while (wrapPos < line.length()) { + if (Character.isSpaceChar(line.charAt(wrapPos))) { + break; + } + wrapPos++; + } + } + + // Extract the substring and recalculate the next search + // start point + String wrappedLine = line.substring(pos, wrapPos).trim(); + sb.append("> " + wrappedLine + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + pos = wrapPos + 1; + } else { + sb.append("> " + line.substring(pos).trim() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + break; + } + } + } + + return sb.toString(); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DateRange.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DateRange.java new file mode 100644 index 000000000..90533ef2a --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DateRange.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.text.DateFormat; +import java.util.Calendar; + +import org.eclipse.core.runtime.Assert; + +/** + * @author Rob Elves + * @since 3.0 + */ +public class DateRange implements Comparable<DateRange> { + protected static final long DAY = 1000 * 60 * 60 * 24; + + private final Calendar startDate; + + private final Calendar endDate; + + /** + * create an instance of a date range that represents a finite point in time + */ + public DateRange(Calendar time) { + startDate = time; + endDate = time; + } + + public DateRange(Calendar startDate, Calendar endDate) { + Assert.isNotNull(startDate); + Assert.isNotNull(endDate); + this.startDate = startDate; + this.endDate = endDate; + } + + public boolean includes(DateRange range) { + return (startDate.getTimeInMillis() <= range.getStartDate().getTimeInMillis()) + && (endDate.getTimeInMillis() >= range.getEndDate().getTimeInMillis()); + } + + public boolean includes(Calendar cal) { + return (startDate.getTimeInMillis() <= cal.getTimeInMillis()) + && (endDate.getTimeInMillis() >= cal.getTimeInMillis()); + } + + public Calendar getStartDate() { + return startDate; + } + + public Calendar getEndDate() { + return endDate; + } + + /** + * TODO: Move into label provider + */ + @Override + public String toString() { + return toString(true); + } + + public String toString(boolean useDayOfWeekForNextWeek) { + return DateFormat.getDateInstance(DateFormat.MEDIUM).format(startDate.getTime()); + /* + " to "+ DateFormat.getDateInstance(DateFormat.MEDIUM).format(endDate.getTime());*/ + } + +// protected DateRange create(int field, int multiplier) { +// Calendar previousStart = (Calendar) getStartDate().clone(); +// Calendar previousEnd = (Calendar) getEndDate().clone(); +// previousStart.add(field, 1 * multiplier); +// previousEnd.add(field, 1 * multiplier); +// return new DateRange(previousStart, previousEnd); +// } + +// public boolean isDay() { +// return ((getEndDate().getTimeInMillis() - getStartDate().getTimeInMillis()) == DAY - 1); +// } +// +// public boolean isWeek() { +// return ((getEndDate().getTimeInMillis() - getStartDate().getTimeInMillis()) == (DAY * 7) - 1); +// } + + public boolean isPresent() { + return this.getStartDate().before(Calendar.getInstance()) && this.getEndDate().after(Calendar.getInstance()); + } + + public boolean isPast() { + return getEndDate().compareTo(Calendar.getInstance()) < 0; + } + + public boolean isFuture() { + return !isPresent() && this.getStartDate().after(Calendar.getInstance()); + } + + public boolean isBefore(DateRange scheduledDate) { + return this.getEndDate().compareTo(scheduledDate.getStartDate()) < 0; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((endDate == null) ? 0 : endDate.hashCode()); + result = prime * result + ((startDate == null) ? 0 : startDate.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof DateRange)) { + return false; + } + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } +// if (getClass() != obj.getClass()) { +// return false; +// } + DateRange other = (DateRange) obj; + if (endDate == null) { + if (other.endDate != null) { + return false; + } + } else if (!endDate.equals(other.endDate)) { + return false; + } + if (startDate == null) { + if (other.startDate != null) { + return false; + } + } else if (!startDate.equals(other.startDate)) { + return false; + } + return true; + } + + public boolean before(Calendar cal) { + return getEndDate().before(cal); + } + + public boolean after(Calendar cal) { + return cal.before(getEndDate()); + } + + public int compareTo(DateRange range) { + if (range.getStartDate().equals(startDate) && range.getEndDate().equals(endDate)) { + return 0; + } else if (includes(range)) { + return 1; + } else if (before(range.getStartDate())) { + return -1; + } else if (after(range.getEndDate())) { + return 1; + } + return -1; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DayDateRange.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DayDateRange.java new file mode 100644 index 000000000..0203ff3bc --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DayDateRange.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.Calendar; + +import org.eclipse.mylyn.internal.provisional.commons.core.CommonMessages; + +/** + * @author Rob Elves + */ +public class DayDateRange extends DateRange { + + public DayDateRange(Calendar startDate, Calendar endDate) { + super(startDate, endDate); + } + + public DayDateRange next() { + return create(Calendar.DAY_OF_YEAR, 1); + } + + public DayDateRange previous() { + return create(Calendar.DAY_OF_YEAR, -1); + } + + protected DayDateRange create(int field, int multiplier) { + Calendar previousStart = (Calendar) getStartDate().clone(); + Calendar previousEnd = (Calendar) getEndDate().clone(); + previousStart.add(field, 1 * multiplier); + previousEnd.add(field, 1 * multiplier); + return new DayDateRange(previousStart, previousEnd); + } + + @Override + public String toString(boolean useDayOfWeekForNextWeek) { + boolean isThisWeek = TaskActivityUtil.getCurrentWeek().includes(this); + Calendar endNextWeek = TaskActivityUtil.getCalendar(); + endNextWeek.add(Calendar.DAY_OF_YEAR, 7); + boolean isNextWeek = TaskActivityUtil.getNextWeek().includes(this) && this.before(endNextWeek); + if (isThisWeek || (useDayOfWeekForNextWeek && isNextWeek)) { + String day = ""; //$NON-NLS-1$ + switch (getStartDate().get(Calendar.DAY_OF_WEEK)) { + case Calendar.MONDAY: + day = CommonMessages.Monday; + break; + case Calendar.TUESDAY: + day = CommonMessages.Tuesday; + break; + case Calendar.WEDNESDAY: + day = CommonMessages.Wednesday; + break; + case Calendar.THURSDAY: + day = CommonMessages.Thursday; + break; + case Calendar.FRIDAY: + day = CommonMessages.Friday; + break; + case Calendar.SATURDAY: + day = CommonMessages.Saturday; + break; + case Calendar.SUNDAY: + day = CommonMessages.Sunday; + break; + } + if (isPresent()) { + return day + Messages.DayDateRange___Today; + } else { + return day; + } + } + return super.toString(useDayOfWeekForNextWeek); + } + + public static boolean isDayRange(Calendar calStart, Calendar calEnd) { + // bug 248683 + long diff = (calEnd.getTimeInMillis() - calStart.getTimeInMillis()) - (DAY - 1); + return Math.abs(diff) <= 60 * 60 * 1000; + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DefaultTaskMapping.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DefaultTaskMapping.java new file mode 100644 index 000000000..a4bbceb6b --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DefaultTaskMapping.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.mylyn.tasks.core.TaskMapping; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; + +/** + * @author Steffen Pingel + */ +public class DefaultTaskMapping extends TaskMapping { + + public Map<String, String> values = new HashMap<String, String>(); + + @Override + public String getDescription() { + return values.get(TaskAttribute.DESCRIPTION); + } + + @Override + public String getSummary() { + return values.get(TaskAttribute.SUMMARY); + } + + public void setDescription(String value) { + values.put(TaskAttribute.DESCRIPTION, value); + } + + public void setSummary(String value) { + values.put(TaskAttribute.SUMMARY, value); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/IRepositoryConstants.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/IRepositoryConstants.java new file mode 100644 index 000000000..5105c9e3c --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/IRepositoryConstants.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +/** + * @author Mik Kersten + * @since 2.0 + */ +public interface IRepositoryConstants { + + public static final String OLD_PROPERTY_SYNCTIME = "synctime"; //$NON-NLS-1$ + + public static final String PROPERTY_SYNCTIMESTAMP = "lastsynctimestamp"; //$NON-NLS-1$ + + public static final String PROPERTY_TIMEZONE = "timezone"; //$NON-NLS-1$ + + public static final String PROPERTY_ENCODING = "encoding"; //$NON-NLS-1$ + + public static final String PROPERTY_VERSION = "version"; //$NON-NLS-1$ + + public static final String PROPERTY_CONNECTOR_KIND = "kind"; //$NON-NLS-1$ + + public static final String PROPERTY_URL = "url"; //$NON-NLS-1$ + + public static final String PROPERTY_LABEL = "label"; //$NON-NLS-1$ + + public static final String PROPERTY_DELIM = ":"; //$NON-NLS-1$ + + public static final String KIND_UNKNOWN = "<unknown>"; //$NON-NLS-1$ + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/IRepositoryModelListener.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/IRepositoryModelListener.java new file mode 100644 index 000000000..1c35c82c9 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/IRepositoryModelListener.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +/** + * @author Mik Kersten + * @author Steffen Pingel + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IRepositoryModelListener { + + public void loaded(); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskJobFactory.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskJobFactory.java new file mode 100644 index 000000000..e2fb1e4d5 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskJobFactory.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.Set; + +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskAttachmentSource; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tasks.core.sync.SubmitJob; +import org.eclipse.mylyn.tasks.core.sync.SynchronizationJob; +import org.eclipse.mylyn.tasks.core.sync.TaskJob; + +/** + * @author Steffen Pingel + * @author Mik Kersten + */ +public interface ITaskJobFactory { + + public abstract SynchronizationJob createSynchronizeTasksJob(AbstractRepositoryConnector connector, + TaskRepository taskRepository, Set<ITask> tasks); + + public abstract SynchronizationJob createSynchronizeQueriesJob(AbstractRepositoryConnector connector, + TaskRepository repository, Set<RepositoryQuery> queries); + + public abstract SynchronizationJob createSynchronizeRepositoriesJob(Set<TaskRepository> repositories); + + public abstract SubmitJob createSubmitTaskJob(AbstractRepositoryConnector connector, TaskRepository taskRepository, + ITask task, TaskData taskData, Set<TaskAttribute> changedAttributes); + + public abstract TaskJob createUpdateRepositoryConfigurationJob(AbstractRepositoryConnector connector, + TaskRepository taskRepository); + + public abstract SubmitJob createSubmitTaskAttachmentJob(AbstractRepositoryConnector connector, + TaskRepository taskRepository, ITask task, AbstractTaskAttachmentSource source, String comment, + TaskAttribute attachmentAttribute); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskList.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskList.java new file mode 100644 index 000000000..83c7eb74a --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskList.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.Set; + +import org.eclipse.mylyn.tasks.core.IRepositoryElement; +import org.eclipse.mylyn.tasks.core.ITask; + +/** + * @author Steffen Pingel + * @author Robert Elves + * @author Mik Kersten + * @since 3.0 + */ +public interface ITaskList { + + public abstract void addChangeListener(ITaskListChangeListener listener); + + public abstract void addQuery(RepositoryQuery query) throws IllegalArgumentException; + + /** + * Add orphaned task to the task list + */ + public abstract void addTask(ITask task) throws IllegalArgumentException; + + /** + * Precondition: {@code container} already exists in tasklist (be it a parent task, category, or query) If the + * parentContainer is null the task is considered an orphan and added to the appropriate repository's orphaned tasks + * container. + * + * @param task + * to be added + * @param container + * task container, query or parent task must not be null + */ + public abstract boolean addTask(ITask task, AbstractTaskContainer parentContainer); + + public abstract void deleteCategory(AbstractTaskCategory category); + + public abstract void deleteQuery(RepositoryQuery query); + + /** + * TODO: refactor around querying containers for their tasks + * + * Task is removed from all containers: root, archive, category, and orphan bin + * + * Currently subtasks are not deleted but rather are rather potentially orphaned + */ + public abstract void deleteTask(ITask task); + + public abstract Set<AbstractTaskCategory> getCategories(); + + public abstract Set<RepositoryQuery> getQueries(); + + /** + * @since 2.0 + */ + public abstract ITask getTask(String repositoryUrl, String taskId); + + /** + * @param task + * list element + */ + public abstract void notifyElementChanged(IRepositoryElement element); + + public abstract void notifySynchronizationStateChanged(IRepositoryElement element); + + public abstract void removeChangeListener(ITaskListChangeListener listener); + + /** + * @since 3.0 + */ + public abstract void removeFromContainer(AbstractTaskContainer container, ITask task); + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskListChangeListener.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskListChangeListener.java new file mode 100644 index 000000000..84cbeef45 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskListChangeListener.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.Set; + +/** + * Listener for task list modifications and task content modifications. + * + * @author Mik Kersten + * @since 2.0 + */ +public interface ITaskListChangeListener { + + public abstract void containersChanged(Set<TaskContainerDelta> containers); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskListRunnable.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskListRunnable.java new file mode 100644 index 000000000..d47651af5 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskListRunnable.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * @author Rob Elves + */ +public interface ITaskListRunnable { + + public void execute(IProgressMonitor monitor) throws CoreException; +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskRepositoryElement.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskRepositoryElement.java new file mode 100644 index 000000000..391ba3792 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskRepositoryElement.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +/** + * @author Steffen Pingel + */ +public interface ITaskRepositoryElement { + + public String getConnectorKind(); + + public String getRepositoryUrl(); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskRepositoryFilter.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskRepositoryFilter.java new file mode 100644 index 000000000..3d9b191d3 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskRepositoryFilter.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Eugene Kuleshov and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Eugene Kuleshov - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.TaskRepository; + +/** + * Task repository filter to build list of repositories with required capabilities. + * + * @author Eugene Kleshov + * @since 2.0 + */ +public interface ITaskRepositoryFilter { + + public static ITaskRepositoryFilter ALL = new ITaskRepositoryFilter() { + public boolean accept(TaskRepository repository, AbstractRepositoryConnector connector) { + return true; + } + }; + + public static ITaskRepositoryFilter CAN_QUERY = new ITaskRepositoryFilter() { + public boolean accept(TaskRepository repository, AbstractRepositoryConnector connector) { + return !(connector instanceof LocalRepositoryConnector) && !repository.isOffline() + && connector.canQuery(repository); + } + }; + + public static ITaskRepositoryFilter CAN_CREATE_NEW_TASK = new ITaskRepositoryFilter() { + public boolean accept(TaskRepository repository, AbstractRepositoryConnector connector) { + return connector.canCreateNewTask(repository) && !repository.isOffline(); + } + }; + + public static ITaskRepositoryFilter CAN_CREATE_TASK_FROM_KEY = new ITaskRepositoryFilter() { + public boolean accept(TaskRepository repository, AbstractRepositoryConnector connector) { + return connector.canCreateTaskFromKey(repository) && !repository.isOffline(); + } + }; + + public static ITaskRepositoryFilter IS_USER_MANAGED = new ITaskRepositoryFilter() { + public boolean accept(TaskRepository repository, AbstractRepositoryConnector connector) { + return connector.isUserManaged(); + } + }; + + public abstract boolean accept(TaskRepository repository, AbstractRepositoryConnector connector); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITasksCoreConstants.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITasksCoreConstants.java new file mode 100644 index 000000000..d0e1b5a01 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITasksCoreConstants.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * @author Rob Elves + */ +public interface ITasksCoreConstants { + + public static final int MAX_SUBTASK_DEPTH = 10; + + public static final String ID_PLUGIN = "org.eclipse.mylyn.tasks.core"; //$NON-NLS-1$ + + public static final String OLD_TASK_LIST_FILE = "tasklist.xml"; //$NON-NLS-1$ + + public static final String FILENAME_ENCODING = "UTF-8"; //$NON-NLS-1$ + + public static final String OLD_PREFIX_TASKLIST = "tasklist"; //$NON-NLS-1$ + + public static final String PREFIX_TASKS = "tasks"; //$NON-NLS-1$ + + public static final String DEFAULT_BACKUP_FOLDER_NAME = "backup"; //$NON-NLS-1$ + + public static final String EXPORT_FILE_NAME = "mylyn-tasks"; //$NON-NLS-1$ + + public static final String FILE_EXTENSION = ".xml.zip"; //$NON-NLS-1$ + + public static final String OLD_FILENAME_TIMESTAMP_FORMAT = "yyyy-MM-dd"; //$NON-NLS-1$ + + public static final String FILENAME_TIMESTAMP_FORMAT = "yyyy-MM-dd-HHmmss"; //$NON-NLS-1$ + + public static final String OLD_M_2_TASKLIST_FILENAME = OLD_PREFIX_TASKLIST + FILE_EXTENSION; + + public static final String DEFAULT_TASK_LIST_FILE = PREFIX_TASKS + FILE_EXTENSION; + + public static final String CONTEXTS_DIRECTORY = "contexts"; //$NON-NLS-1$ + + public static final ISchedulingRule ACTIVITY_SCHEDULING_RULE = new MutexSchedulingRule(); + + public static final ISchedulingRule TASKLIST_SCHEDULING_RULE = new MutexSchedulingRule(); + + public static final ISchedulingRule ROOT_SCHEDULING_RULE = new RootSchedulingRule(); + + public static final String ATTRIBUTE_OUTGOING_NEW_REPOSITORY_URL = "outgoingNewRepositoryUrl"; //$NON-NLS-1$ + + public static final String ATTRIBUTE_OUTGOING_NEW_CONNECTOR_KIND = "outgoingNewConnectorKind"; //$NON-NLS-1$ + + /** + * Jobs that have the same instances of this rule set are mutually exclusive. + */ + public static class MutexSchedulingRule extends RootSchedulingRule { + + public MutexSchedulingRule() { + } + + @Override + public boolean contains(ISchedulingRule rule) { + return rule == this; + } + + @Override + public boolean isConflicting(ISchedulingRule rule) { + return rule == ROOT_SCHEDULING_RULE || rule == this; + } + } + + /** + * The parent of all scheduling rules that modify task data. + * + * @see ITasksCoreConstants#ROOT_SCHEDULING_RULE + */ + public static class RootSchedulingRule implements ISchedulingRule { + + protected RootSchedulingRule() { + } + + public boolean contains(ISchedulingRule rule) { + return rule instanceof RootSchedulingRule; + } + + public boolean isConflicting(ISchedulingRule rule) { + return rule instanceof RootSchedulingRule; + } + } + + /** + * Jobs that have an instances of this rule set which references the same object are mutually exclusive. + */ + public static class ObjectSchedulingRule extends RootSchedulingRule { + + private final Object object; + + public ObjectSchedulingRule(Object object) { + Assert.isNotNull(object); + this.object = object; + } + + @Override + public boolean contains(ISchedulingRule rule) { + return rule == this; + } + + @Override + public boolean isConflicting(ISchedulingRule rule) { + if (rule == ROOT_SCHEDULING_RULE) { + return true; + } + if (rule instanceof ObjectSchedulingRule) { + return object.equals(((ObjectSchedulingRule) rule).object); + } + return false; + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [" + object + "]"; //$NON-NLS-1$ //$NON-NLS-2$ + } + + } + + public static final String COMMAND_LINE_NO_ACTIVATE_TASK = "-no-activate-task"; //$NON-NLS-1$ + + public static final String PROPERTY_LINK_PROVIDER_TIMEOUT = "org.eclipse.mylyn.linkProviderTimeout"; //$NON-NLS-1$ + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITransferList.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITransferList.java new file mode 100644 index 000000000..a08c4e1e3 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITransferList.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.Collection; +import java.util.Set; + +import org.eclipse.mylyn.tasks.core.ITask; + +/** + * Minimal task list interface required for externalization. + * + * @author Steffen Pingel + */ +public interface ITransferList { + + public abstract void addCategory(TaskCategory category); + + public abstract void addQuery(RepositoryQuery query); + + public abstract void addTask(ITask task); + + public abstract boolean addTask(ITask task, AbstractTaskContainer parentContainer); + + public AbstractTaskCategory getContainerForHandle(String handle); + + public abstract Collection<AbstractTask> getAllTasks(); + + public abstract Set<AbstractTaskCategory> getCategories(); + + public abstract Set<RepositoryQuery> getQueries(); + + public AbstractTask getTask(String handleIdentifier); + + public abstract ITask getTask(String repositoryUrl, String taskId); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/LocalRepositoryConnector.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/LocalRepositoryConnector.java new file mode 100644 index 000000000..39dc4ad20 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/LocalRepositoryConnector.java @@ -0,0 +1,118 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.IRepositoryQuery; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tasks.core.data.TaskDataCollector; +import org.eclipse.mylyn.tasks.core.sync.ISynchronizationSession; + +/** + * @author Rob Elves + */ +public class LocalRepositoryConnector extends AbstractRepositoryConnector { + + public static final String REPOSITORY_LABEL = Messages.LocalRepositoryConnector_Local; + + public static final String CONNECTOR_KIND = "local"; //$NON-NLS-1$ + + public static final String REPOSITORY_URL = "local"; //$NON-NLS-1$ + + public static final String REPOSITORY_VERSION = "1"; //$NON-NLS-1$ + + public static final String DEFAULT_SUMMARY = Messages.LocalRepositoryConnector_New_Task; + + @Override + public boolean canCreateNewTask(TaskRepository repository) { + return true; + } + + @Override + public boolean canCreateTaskFromKey(TaskRepository repository) { + return false; + } + + @Override + public String getLabel() { + return Messages.LocalRepositoryConnector_Local_Task_Repository; + } + + @Override + public String getConnectorKind() { + return CONNECTOR_KIND; + } + + @Override + public String getRepositoryUrlFromTaskUrl(String taskFullUrl) { + // ignore + return null; + } + + @Override + public String getTaskIdFromTaskUrl(String taskFullUrl) { + // ignore + return null; + } + + @Override + public String getTaskUrl(String repositoryUrl, String taskId) { + // ignore + return null; + } + + @Override + public IStatus performQuery(TaskRepository repository, IRepositoryQuery query, TaskDataCollector resultCollector, + ISynchronizationSession event, IProgressMonitor monitor) { + // ignore + return null; + } + + @Override + public void updateRepositoryConfiguration(TaskRepository repository, IProgressMonitor monitor) throws CoreException { + // ignore + } + + @Override + public boolean isUserManaged() { + return false; + } + + @Override + public TaskData getTaskData(TaskRepository taskRepository, String taskId, IProgressMonitor monitor) + throws CoreException { + // ignore + return null; + } + + @Override + public boolean hasTaskChanged(TaskRepository taskRepository, ITask task, TaskData taskData) { + // ignore + return false; + } + + @Override + public void updateTaskFromTaskData(TaskRepository repository, ITask task, TaskData taskData) { + // ignore + } + + @Override + public boolean hasLocalCompletionState(TaskRepository taskRepository, ITask task) { + return true; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/LocalTask.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/LocalTask.java new file mode 100644 index 000000000..afa5deddb --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/LocalTask.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +/** + * @author Rob Elves + */ +public class LocalTask extends AbstractTask { + + public static final String SYNC_DATE_NOW = "now"; //$NON-NLS-1$ + + public LocalTask(String taskId, String summary) { + super(LocalRepositoryConnector.REPOSITORY_URL, taskId, summary); + } + + @Override + public boolean isLocal() { + return true; + } + + @Override + public String getConnectorKind() { + return LocalRepositoryConnector.CONNECTOR_KIND; + } + + @Override + public boolean isNotified() { + return true; + } + + @Override + public String getLastReadTimeStamp() { + return SYNC_DATE_NOW; + } + + @Override + public String getOwner() { + return LocalRepositoryConnector.CONNECTOR_KIND; + } + + @Override + public String getTaskKey() { + return null; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/Messages.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/Messages.java new file mode 100644 index 000000000..c328f7835 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/Messages.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.mylyn.internal.tasks.core.messages"; //$NON-NLS-1$ + + static { + // load message values from bundle file + reloadMessages(); + } + + public static void reloadMessages() { + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + public static String DayDateRange___Today; + + public static String LocalRepositoryConnector_Local; + + public static String LocalRepositoryConnector_Local_Task_Repository; + + public static String LocalRepositoryConnector_New_Task; + + public static String RepositoryExternalizationParticipant_Task_Repositories; + + public static String TaskRepositoryManager_No_repository_available; + + public static String UncategorizedTaskContainer_Uncategorized; + + public static String UnmatchedTaskContainer_Unmatched; + + public static String UnsubmittedTaskContainer_Unsubmitted; + + public static String WeekDateRange_Next_Week; + + public static String WeekDateRange_Previous_Week; + + public static String WeekDateRange_This_Week; + + public static String WeekDateRange_Two_Weeks; + + public static String PriorityLevel_High; + + public static String PriorityLevel_Low; + + public static String PriorityLevel_Normal; + + public static String PriorityLevel_Very_High; + + public static String PriorityLevel_Very_Low; + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/Person.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/Person.java new file mode 100644 index 000000000..79088d5b8 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/Person.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +/** + * @author Rob Elves + */ +public class Person extends AbstractTaskContainer implements ITaskRepositoryElement { + + private final String connectorKind; + + private final String repositoryUrl; + + public Person(String email, String connectorKind, String repositoryUrl) { + super(email); + this.connectorKind = connectorKind; + this.repositoryUrl = repositoryUrl; + } + + public String getConnectorKind() { + return connectorKind; + } + + public String getRepositoryUrl() { + return repositoryUrl; + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryExternalizationParticipant.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryExternalizationParticipant.java new file mode 100644 index 000000000..5b92741f1 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryExternalizationParticipant.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.io.File; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.mylyn.internal.tasks.core.externalization.AbstractExternalizationParticipant; +import org.eclipse.mylyn.internal.tasks.core.externalization.ExternalizationManager; +import org.eclipse.mylyn.tasks.core.IRepositoryListener; +import org.eclipse.mylyn.tasks.core.TaskRepository; + +/** + * @author Rob Elves + * @since 3.0 + */ +public class RepositoryExternalizationParticipant extends AbstractExternalizationParticipant implements + IRepositoryListener { + + private static final String DESCRIPTION = Messages.RepositoryExternalizationParticipant_Task_Repositories; + + private final TaskRepositoryManager repositoryManager; + + private final ExternalizationManager externalizationManager; + + private boolean dirty = false; + + public RepositoryExternalizationParticipant(ExternalizationManager exManager, TaskRepositoryManager manager) { + this.repositoryManager = manager; + this.externalizationManager = exManager; + this.repositoryManager.addListener(this); + } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public ISchedulingRule getSchedulingRule() { + return ITasksCoreConstants.TASKLIST_SCHEDULING_RULE; + } + + @Override + public boolean isDirty() { + return dirty; + } + + private void requestSave() { + synchronized (RepositoryExternalizationParticipant.this) { + dirty = true; + } + externalizationManager.requestSave(); + } + + @Override + public void load(File sourceFile, IProgressMonitor monitor) throws CoreException { + repositoryManager.readRepositories(sourceFile.getAbsolutePath()); + } + + @Override + public void save(File targetFile, IProgressMonitor monitor) throws CoreException { + synchronized (RepositoryExternalizationParticipant.this) { + dirty = false; + } + repositoryManager.saveRepositories(targetFile.getAbsolutePath()); + } + + @Override + public String getFileName() { + return TaskRepositoryManager.DEFAULT_REPOSITORIES_FILE; + } + + public void repositoryUrlChanged(TaskRepository repository, String oldUrl) { + requestSave(); + } + + public void repositoriesRead() { + // ignore + } + + public void repositoryAdded(TaskRepository repository) { + requestSave(); + } + + public void repositoryRemoved(TaskRepository repository) { + requestSave(); + } + + public void repositorySettingsChanged(TaskRepository repository) { + requestSave(); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryModel.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryModel.java new file mode 100644 index 000000000..9bd519fc1 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryModel.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + * David Green - fixes for bug 265682 + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.Map; +import java.util.WeakHashMap; + +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.mylyn.tasks.core.IRepositoryModel; +import org.eclipse.mylyn.tasks.core.IRepositoryQuery; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITaskAttachment; +import org.eclipse.mylyn.tasks.core.ITaskComment; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskData; + +/** + * @author Steffen Pingel + */ +public class RepositoryModel implements IRepositoryModel { + + private final IRepositoryManager repositoryManager; + + private final Map<String, ITask> taskByHandle = new WeakHashMap<String, ITask>(); + + private final TaskList taskList; + + public RepositoryModel(TaskList taskList, IRepositoryManager repositoryManager) { + this.taskList = taskList; + this.repositoryManager = repositoryManager; + initialize(); + } + + private void initialize() { + repositoryManager.addListener(new TaskRepositoryAdapter() { + @Override + public void repositoryAdded(TaskRepository repository) { + taskList.addUnmatchedContainer(new UnmatchedTaskContainer(repository.getConnectorKind(), + repository.getRepositoryUrl())); + } + + @Override + public void repositoryRemoved(TaskRepository repository) { + // TODO + //taskList.removeUnmatchedContainer(taskList.getUnmatchedContainer(repository.getRepositoryUrl())); + } + }); + } + + public IRepositoryQuery createRepositoryQuery(TaskRepository taskRepository) { + String handle = taskList.getUniqueHandleIdentifier(); + RepositoryQuery query = new RepositoryQuery(taskRepository.getConnectorKind(), handle); + query.setRepositoryUrl(taskRepository.getRepositoryUrl()); + return query; + } + + public synchronized ITask createTask(TaskRepository taskRepository, String taskId) { + String handle = getTaskHandle(taskRepository, taskId); + ITask task = taskByHandle.get(handle); + if (task == null) { + task = new TaskTask(taskRepository.getConnectorKind(), taskRepository.getRepositoryUrl(), taskId); + taskByHandle.put(handle, task); + } + return task; + } + + public ITaskAttachment createTaskAttachment(TaskAttribute taskAttribute) { + TaskData taskData = taskAttribute.getTaskData(); + TaskRepository taskRepository = repositoryManager.getRepository(taskData.getConnectorKind(), + taskData.getRepositoryUrl()); + ITask task = getTask(taskRepository, taskData.getTaskId()); + if (task == null) { + return null; + } + TaskAttachment taskAttachment = new TaskAttachment(taskRepository, task, taskAttribute); + taskData.getAttributeMapper().updateTaskAttachment(taskAttachment, taskAttribute); + return taskAttachment; + } + + public ITaskComment createTaskComment(TaskAttribute taskAttribute) { + TaskData taskData = taskAttribute.getTaskData(); + TaskRepository taskRepository = repositoryManager.getRepository(taskData.getConnectorKind(), + taskData.getRepositoryUrl()); + ITask task = getTask(taskRepository, taskData.getTaskId()); + if (task == null) { + return null; + } + TaskComment taskComment = new TaskComment(taskRepository, task, taskAttribute); + taskData.getAttributeMapper().updateTaskComment(taskComment, taskAttribute); + return taskComment; + } + + public synchronized ITask getTask(String handleIdentifier) { + ITask task = taskByHandle.get(handleIdentifier); + if (task == null) { + task = taskList.getTask(handleIdentifier); + } + return task; + } + + public synchronized ITask getTask(TaskRepository taskRepository, String taskId) { + return getTask(getTaskHandle(taskRepository, taskId)); + } + + public synchronized ITask getTaskByKey(TaskRepository repository, String taskKey) { + return taskList.getTaskByKey(repository.getUrl(), taskKey); + } + + private String getTaskHandle(TaskRepository taskRepository, String taskId) { + return RepositoryTaskHandleUtil.getHandle(taskRepository.getRepositoryUrl(), taskId); + } + + public TaskRepository getTaskRepository(String connectorKind, String repositoryUrl) { + TaskRepository taskRepository = repositoryManager.getRepository(connectorKind, repositoryUrl); + if (taskRepository == null) { + taskRepository = new TaskRepository(connectorKind, repositoryUrl); + repositoryManager.addRepository(taskRepository); + } + return taskRepository; + } + + public synchronized void clear() { + taskByHandle.clear(); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryPerson.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryPerson.java new file mode 100644 index 000000000..87358d841 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryPerson.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import org.eclipse.mylyn.tasks.core.IRepositoryPerson; +import org.eclipse.mylyn.tasks.core.TaskRepository; + +/** + * @author Steffen Pingel + */ +public class RepositoryPerson implements IRepositoryPerson { + + private String name; + + private final String personId; + + private final TaskRepository taskRepository; + + public RepositoryPerson(TaskRepository taskRepository, String personId) { + this.taskRepository = taskRepository; + this.personId = personId; + } + + public String getConnectorKind() { + return taskRepository.getConnectorKind(); + } + + public String getName() { + return name; + } + + public String getPersonId() { + return personId; + } + + public String getRepositoryUrl() { + return taskRepository.getRepositoryUrl(); + } + + public TaskRepository getTaskRepository() { + return taskRepository; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + if (getName() == null) { + return getPersonId(); + } else { + return getName() + " <" + getPersonId() + ">"; //$NON-NLS-1$ //$NON-NLS-2$ + } + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryQuery.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryQuery.java new file mode 100644 index 000000000..436e64aa2 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryQuery.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + * Eugene Kuleshov - improvements + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.Collections; +import java.util.Map; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.mylyn.tasks.core.IRepositoryQuery; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITask.PriorityLevel; + +/** + * @author Mik Kersten + * @author Eugene Kuleshov + * @author Rob Elves + */ +public class RepositoryQuery extends AbstractTaskContainer implements IRepositoryQuery, ITaskRepositoryElement { + + private final String connectorKind; + + protected String lastSynchronizedStamp = "<never>"; //$NON-NLS-1$ + + protected String repositoryUrl; + + protected IStatus status; + + private boolean synchronizing; + + private String summary; + + private AttributeMap attributeMap; + + public RepositoryQuery(String connectorKind, String handle) { + super(handle); + this.connectorKind = connectorKind; + setSummary(handle); + } + + public String getConnectorKind() { + return connectorKind; + } + + // TODO: should be a date + public String getLastSynchronizedTimeStamp() { + return lastSynchronizedStamp; + } + + @Override + public String getPriority() { + if (super.isEmpty()) { + return PriorityLevel.P1.toString(); + } + String highestPriority = PriorityLevel.P5.toString(); + for (ITask hit : getChildren()) { + if (highestPriority.compareTo(hit.getPriority()) > 0) { + highestPriority = hit.getPriority(); + } + } + return highestPriority; + } + + public String getRepositoryUrl() { + return repositoryUrl; + } + + public IStatus getStatus() { + return status; + } + + // TODO: move higher up and merge with AbstractTask + public boolean isSynchronizing() { + return synchronizing; + } + + public void setLastSynchronizedStamp(String lastRefreshTimeStamp) { + this.lastSynchronizedStamp = lastRefreshTimeStamp; + } + + public void setRepositoryUrl(String newRepositoryUrl) { + String url = getUrl(); + if (repositoryUrl != null && url != null && url.startsWith(repositoryUrl)) { + // change corresponding part of the query URL + setUrl(newRepositoryUrl + url.substring(repositoryUrl.length())); + } + this.repositoryUrl = newRepositoryUrl; + } + + public void setStatus(IStatus status) { + this.status = status; + } + + public void setSynchronizing(boolean synchronizing) { + this.synchronizing = synchronizing; + } + + @Override + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public synchronized String getAttribute(String key) { + return (attributeMap != null) ? attributeMap.getAttribute(key) : null; + } + + public synchronized Map<String, String> getAttributes() { + if (attributeMap != null) { + return attributeMap.getAttributes(); + } else { + return Collections.emptyMap(); + } + } + + public synchronized void setAttribute(String key, String value) { + if (attributeMap == null) { + attributeMap = new AttributeMap(); + } + attributeMap.setAttribute(key, value); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryTaskHandleUtil.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryTaskHandleUtil.java new file mode 100644 index 000000000..a02978a1d --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryTaskHandleUtil.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +/** + * @author Mik Kersten + */ +public class RepositoryTaskHandleUtil { + + public static final String HANDLE_DELIM = "-"; //$NON-NLS-1$ + + private static final String MISSING_REPOSITORY = "norepository"; //$NON-NLS-1$ + + public static String getHandle(String repositoryUrl, String taskId) { + if (!isValidTaskId(taskId)) { + throw new RuntimeException("invalid handle for task, can not contain: " + HANDLE_DELIM + ", was: " + taskId); //$NON-NLS-1$ //$NON-NLS-2$ + } + + if (repositoryUrl == null) { + return MISSING_REPOSITORY + HANDLE_DELIM + taskId; + } else { + return (repositoryUrl + HANDLE_DELIM + taskId).intern(); + } + } + + public static String getRepositoryUrl(String taskHandle) { + int index = taskHandle.lastIndexOf(RepositoryTaskHandleUtil.HANDLE_DELIM); + String url = null; + if (index != -1) { + url = taskHandle.substring(0, index); + } + return url; + } + + public static String getTaskId(String taskHandle) { + int index = taskHandle.lastIndexOf(HANDLE_DELIM); + if (index != -1) { + String id = taskHandle.substring(index + 1); + return id; + } + return null; + } + + public static boolean isValidTaskId(String taskId) { + return !taskId.contains(HANDLE_DELIM); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryTemplateManager.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryTemplateManager.java new file mode 100644 index 000000000..5ae388aab --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryTemplateManager.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.eclipse.mylyn.tasks.core.RepositoryTemplate; + +/** + * @author Steffen Pingel + */ +public class RepositoryTemplateManager { + + private Map<String, Set<RepositoryTemplate>> templateByConnectorKind; + + public synchronized void addTemplate(String connectorKind, RepositoryTemplate template) { + getTemplates(connectorKind).add(template); + } + + public synchronized Set<RepositoryTemplate> getTemplates(String connectorKind) { + if (templateByConnectorKind == null) { + templateByConnectorKind = new HashMap<String, Set<RepositoryTemplate>>(); + } + Set<RepositoryTemplate> templates = templateByConnectorKind.get(connectorKind); + if (templates == null) { + templates = new LinkedHashSet<RepositoryTemplate>(); + templateByConnectorKind.put(connectorKind, templates); + } + return templates; + } + + public synchronized void removeTemplate(String connectorKind, RepositoryTemplate template) { + getTemplates(connectorKind).remove(template); + } + + /** + * Returns null if template not found. + */ + public synchronized RepositoryTemplate getTemplate(String connectorKind, String label) { + for (RepositoryTemplate template : getTemplates(connectorKind)) { + if (template.label.equals(label)) { + return template; + } + } + return null; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/SaxRepositoriesContentHandler.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/SaxRepositoriesContentHandler.java new file mode 100644 index 000000000..220a3d5a4 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/SaxRepositoriesContentHandler.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Adapted from SaxContextContentHandler + * + * @author Rob Elves + */ +public class SaxRepositoriesContentHandler extends DefaultHandler { + + static final String ATTRIBUTE_INTERACTION_EVENT = "InteractionEvent"; //$NON-NLS-1$ + + private final Set<TaskRepository> taskRepositories = new HashSet<TaskRepository>(); + + @SuppressWarnings( { "deprecation", "restriction" }) + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + try { + if (localName.equals(TaskRepositoriesExternalizer.ELEMENT_TASK_REPOSITORY) && attributes != null) { + String kind = org.eclipse.mylyn.internal.commons.core.XmlStringConverter.convertXmlToString(attributes.getValue(IRepositoryConstants.PROPERTY_CONNECTOR_KIND)); + String url = org.eclipse.mylyn.internal.commons.core.XmlStringConverter.convertXmlToString(attributes.getValue(IRepositoryConstants.PROPERTY_URL)); + if (kind != null && kind.length() > 0 && url != null && url.length() > 0) { + TaskRepository repository = new TaskRepository(kind, url); + for (int index = 0; index < attributes.getLength(); index++) { + String key = org.eclipse.mylyn.internal.commons.core.XmlStringConverter.convertXmlToString(attributes.getLocalName(index)); + String value = org.eclipse.mylyn.internal.commons.core.XmlStringConverter.convertXmlToString(attributes.getValue(index)); + repository.setProperty(key, value); + } + taskRepositories.add(repository); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + } + + public Set<TaskRepository> getRepositories() { + return taskRepositories; + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/SaxRepositoriesWriter.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/SaxRepositoriesWriter.java new file mode 100644 index 000000000..1bae4650a --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/SaxRepositoriesWriter.java @@ -0,0 +1,193 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stream.StreamResult; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.xml.sax.ContentHandler; +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.AttributesImpl; + +/** + * Adapted from SaxContextWriter + * + * @author Rob Elves + */ +public class SaxRepositoriesWriter { + + private OutputStream outputStream; + + public void setOutputStream(OutputStream outputStream) { + this.outputStream = outputStream; + } + + public void writeRepositoriesToStream(Collection<TaskRepository> repositories) throws IOException { + if (outputStream == null) { + IOException ioe = new IOException("OutputStream not set"); //$NON-NLS-1$ + throw ioe; + } + + try { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.transform( + new SAXSource(new RepositoriesWriter(), new TaskRepositoriesInputSource(repositories)), + new StreamResult(outputStream)); + } catch (TransformerException e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Could not write repositories", //$NON-NLS-1$ + e)); + throw new IOException(e.getMessage()); + } + + } + + private static class TaskRepositoriesInputSource extends InputSource { + private final Collection<TaskRepository> repositories; + + public TaskRepositoriesInputSource(Collection<TaskRepository> repositories) { + this.repositories = repositories; + } + + public Collection<TaskRepository> getRepositories() { + return this.repositories; + } + + } + + private static class RepositoriesWriter implements XMLReader { + +// private static final String ELEMENT_TASK_REPOSITORIES = "TaskRepositories"; +// +// public static final String ELEMENT_TASK_REPOSITORY = "TaskRepository"; +// +// private static final String ATTRIBUTE_VERSION = "xmlVersion"; + +// private static final String ATTRIBUTE_URL = "Url"; +// +// private static final String ATTRIBUTE_KIND = "Kind"; + + private ContentHandler handler; + + private ErrorHandler errorHandler; + + public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException { + return false; + } + + public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException { + + } + + public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException { + return null; + } + + public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException { + } + + public void setEntityResolver(EntityResolver resolver) { + } + + public EntityResolver getEntityResolver() { + return null; + } + + public void setDTDHandler(DTDHandler handler) { + } + + public DTDHandler getDTDHandler() { + return null; + } + + public void setContentHandler(ContentHandler handler) { + this.handler = handler; + + } + + public ContentHandler getContentHandler() { + return handler; + } + + public void setErrorHandler(ErrorHandler handler) { + this.errorHandler = handler; + + } + + public ErrorHandler getErrorHandler() { + return errorHandler; + } + + @SuppressWarnings( { "deprecation", "restriction" }) + public void parse(InputSource input) throws IOException, SAXException { + if (!(input instanceof TaskRepositoriesInputSource)) { + throw new SAXException("Can only parse writable input sources"); //$NON-NLS-1$ + } + + Collection<TaskRepository> repositories = ((TaskRepositoriesInputSource) input).getRepositories(); + + handler.startDocument(); + AttributesImpl rootAttributes = new AttributesImpl(); + rootAttributes.addAttribute("", TaskRepositoriesExternalizer.ATTRIBUTE_VERSION, //$NON-NLS-1$ + TaskRepositoriesExternalizer.ATTRIBUTE_VERSION, "", "1"); //$NON-NLS-1$ //$NON-NLS-2$ + + handler.startElement("", TaskRepositoriesExternalizer.ELEMENT_TASK_REPOSITORIES, //$NON-NLS-1$ + TaskRepositoriesExternalizer.ELEMENT_TASK_REPOSITORIES, rootAttributes); + + for (TaskRepository repository : new ArrayList<TaskRepository>(repositories)) { + + AttributesImpl ieAttributes = new AttributesImpl(); + for (String key : repository.getProperties().keySet()) { + ieAttributes.addAttribute( + "", //$NON-NLS-1$ + key, + key, + "", //$NON-NLS-1$ + org.eclipse.mylyn.internal.commons.core.XmlStringConverter.convertToXmlString(repository.getProperties() + .get(key))); + } + + handler.startElement("", TaskRepositoriesExternalizer.ELEMENT_TASK_REPOSITORY, //$NON-NLS-1$ + TaskRepositoriesExternalizer.ELEMENT_TASK_REPOSITORY, ieAttributes); + handler.endElement("", TaskRepositoriesExternalizer.ELEMENT_TASK_REPOSITORY, //$NON-NLS-1$ + TaskRepositoriesExternalizer.ELEMENT_TASK_REPOSITORY); + } + handler.endElement("", TaskRepositoriesExternalizer.ELEMENT_TASK_REPOSITORIES, //$NON-NLS-1$ + TaskRepositoriesExternalizer.ELEMENT_TASK_REPOSITORIES); + + handler.endDocument(); + } + + public void parse(String systemId) throws IOException, SAXException { + throw new SAXException("Can only parse writable input sources"); //$NON-NLS-1$ + } + + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ScheduledTaskContainer.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ScheduledTaskContainer.java new file mode 100644 index 000000000..26a669b1d --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ScheduledTaskContainer.java @@ -0,0 +1,221 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.Calendar; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.mylyn.tasks.core.IRepositoryElement; +import org.eclipse.mylyn.tasks.core.ITask; + +/** + * @author Rob Elves + * @author Mik Kersten + */ +public class ScheduledTaskContainer extends AbstractTaskContainer { + + private final TaskActivityManager activityManager; + + private final String summary; + + private final DateRange range; + + public ScheduledTaskContainer(TaskActivityManager activityManager, DateRange range, String summary) { + super(summary == null ? range.toString(false) : summary); + this.activityManager = activityManager; + this.range = range; + if (summary == null) { + this.summary = range.toString(false); + } else { + this.summary = summary; + } + } + + public ScheduledTaskContainer(TaskActivityManager taskActivityManager, DateRange day) { + this(taskActivityManager, day, null); + } + + public boolean isFuture() { + return !isPresent() && range.getStartDate().after(Calendar.getInstance()); + } + + public boolean isPresent() { + return range.getStartDate().before(Calendar.getInstance()) && range.getEndDate().after(Calendar.getInstance()); + } + +// public boolean isWeekDay() { +// return TaskActivityUtil.getCurrentWeek().isCurrentWeekDay(range); +// } + +// public boolean isToday() { +// if (range instanceof DayDateRange) { +// return ((DayDateRange) range).isToday(); +// } +// return false; +// } + +// public Collection<ITask> getChildren() { +// Set<ITask> children = new HashSet<ITask>(); +// Calendar beginning = TaskActivityUtil.getCalendar(); +// beginning.setTimeInMillis(0); +// if (isFloating() && !isFuture()) { +// for (ITask task : activityManager.getScheduledTasks(rangebeginning, getEndDate())) { +// if (task.internalIsFloatingScheduledDate()) { +// children.add(task); +// } +// } +// } else if (isPresent()) { +// // add all due/overdue +// Calendar end = TaskActivityUtil.getCalendar(); +// end.set(5000, 12, 1); +// for (ITask task : activityManager.getDueTasks(beginning, getEndDate())) { +// if (activityManager.isOwnedByUser(task)) { +// children.add(task); +// } +// } +// +// // add all scheduled/overscheduled +// for (ITask task : activityManager.getScheduledTasks(beginning, getEndDate())) { +// if (!task.internalIsFloatingScheduledDate() && !task.isCompleted()) { +// children.add(task); +// } +// } +// +// // if not scheduled or due in future, and is active, place in today bin +// ITask activeTask = activityManager.getActiveTask(); +// if (activeTask != null && !children.contains(activeTask)) { +// Set<ITask> futureScheduled = activityManager.getScheduledTasks(getStartDate(), end); +// for (ITask task : activityManager.getDueTasks(getStartDate(), end)) { +// if (activityManager.isOwnedByUser(task)) { +// futureScheduled.add(task); +// } +// } +// if (!futureScheduled.contains(activeTask)) { +// children.add(activeTask); +// } +// } +// } else if (isFuture()) { +// children.addAll(activityManager.getScheduledTasks(getStartDate(), getEndDate())); +// for (ITask task : activityManager.getDueTasks(getStartDate(), getEndDate())) { +// if (activityManager.isOwnedByUser(task)) { +// children.add(task); +// } +// } +// } else { +// children.addAll(activityManager.getActiveTasks(range.getStartDate(), range.getEndDate())); +// } +// return children; +// } + + @Override + public Collection<ITask> getChildren() { + + // TODO: Cache this information until the next modification to pertinent data + + Set<ITask> children = new HashSet<ITask>(); + + // All tasks scheduled for this date range + for (ITask task : activityManager.getScheduledTasks(range)) { + if (!task.isCompleted() + || (task.isCompleted() && TaskActivityUtil.getDayOf(task.getCompletionDate()).isPresent())) { + children.add(task); + } + } + + // Add due tasks if not the This Week container + if (!(range instanceof WeekDateRange && ((WeekDateRange) range).isPresent())) { + for (ITask task : activityManager.getDueTasks(range.getStartDate(), range.getEndDate())) { + if (activityManager.isOwnedByUser(task)) { + children.add(task); + } + } + } + + // All over due/scheduled tasks are present in the Today folder + if ((range instanceof DayDateRange) && ((DayDateRange) range).isPresent()) { + for (ITask task : activityManager.getOverScheduledTasks()) { + if (task instanceof AbstractTask + && !(((AbstractTask) task).getScheduledForDate() instanceof WeekDateRange)) { + children.add(task); + } + } + children.addAll(activityManager.getOverDueTasks()); + // if not scheduled or due in future, and is active, place in today bin + ITask activeTask = activityManager.getActiveTask(); + if (activeTask != null && !children.contains(activeTask)) { + children.add(activeTask); + } + } + + if (range instanceof WeekDateRange && ((WeekDateRange) range).isThisWeek()) { + for (ITask task : activityManager.getOverScheduledTasks()) { + if (task instanceof AbstractTask + && ((AbstractTask) task).getScheduledForDate() instanceof WeekDateRange) { + children.add(task); + } + } + } + + return children; + } + + @Override + public String getSummary() { + if (summary != null) { + return summary; + } + return range.toString(); + } + + @Override + public String getHandleIdentifier() { + return summary; + } + + @Override + public String getPriority() { + return ""; //$NON-NLS-1$ + } + + @Override + public String getUrl() { + return ""; //$NON-NLS-1$ + } + + @Override + public int compareTo(IRepositoryElement element) { + if (element instanceof ScheduledTaskContainer) { + ScheduledTaskContainer container = ((ScheduledTaskContainer) element); + return range.compareTo(container.getDateRange()); + } + return 0; + } + + public DateRange getDateRange() { + return range; + } + + public Calendar getEnd() { + return range.getEndDate(); + } + + public Calendar getStart() { + return range.getStartDate(); + } + + public boolean includes(Calendar pastWeeksTaskStart) { + return range.includes(pastWeeksTaskStart); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivationHistory.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivationHistory.java new file mode 100644 index 000000000..8828f7dbc --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivationHistory.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + * Ken Sueda - original prototype + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITaskContainer; + +/** + * Maintains a list of tasks that have been activated in the past. Each task only occurs once in the list. The list is + * sorted by most recent activation, i.e. the task with the highest index is the task that was most recently activated. + * + * @author Wesley Coelho (Added persistent tasks) + * @author Mik Kersten (hardening) + * @author Rob Elves + * @author Steffen Pingel + */ +public class TaskActivationHistory { + + /** + * The most recently activated task has the highest index in the list. + */ + private final List<AbstractTask> history = new ArrayList<AbstractTask>(); + + /** + * Index pointing to the task that was previously active. + */ + private int previousIndex = -1; + + public void addTask(AbstractTask task) { + boolean isPreviousTask = false; + // optimization: do not modify list, if task is already last + if (history.isEmpty() || history.get(history.size() - 1) != task) { + if (previousIndex >= 0 && previousIndex < history.size() && history.get(previousIndex) == task) { + isPreviousTask = true; + } + history.remove(task); + history.add(task); + } + if (isPreviousTask) { + // the previous task was activated, move the cursor + previousIndex--; + } else { + previousIndex = history.size() - 2; + } + } + + public boolean containsTask(ITask task) { + return history.contains(task); + } + + public boolean removeTask(ITask task) { + return history.remove(task); + } + + public AbstractTask getPreviousTask() { + if (history.isEmpty()) { + return null; + } + AbstractTask currentTask = history.get(history.size() - 1); + if (currentTask.isActive() && previousIndex >= 0 && previousIndex < history.size()) { + return history.get(previousIndex); + } else { + return currentTask; + } + } + + public List<AbstractTask> getPreviousTasks() { + return Collections.unmodifiableList(new ArrayList<AbstractTask>(history)); + } + + /** + * Returns task activation history for tasks present in <code>containers</code> + */ + public List<AbstractTask> getPreviousTasks(Set<AbstractTaskContainer> containers) { + if (containers.isEmpty()) { + return getPreviousTasks(); + } + Set<ITask> allWorkingSetTasks = new HashSet<ITask>(); + for (ITaskContainer container : containers) { + allWorkingSetTasks.addAll(container.getChildren()); + } + List<AbstractTask> allScopedTasks = new ArrayList<AbstractTask>(getPreviousTasks()); + for (Iterator<AbstractTask> it = allScopedTasks.iterator(); it.hasNext();) { + AbstractTask task = it.next(); + if (!allWorkingSetTasks.contains(task)) { + it.remove(); + } + } + return Collections.unmodifiableList(allScopedTasks); + } + + public boolean hasPrevious() { + return getPreviousTask() != null; + } + + public void clear() { + history.clear(); + previousIndex = -1; + } + + public int indexOf(ITask task) { + return history.indexOf(task); + + } + + public int getSize() { + return history.size(); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivityManager.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivityManager.java new file mode 100644 index 000000000..ffc37c1a2 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivityManager.java @@ -0,0 +1,849 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITaskActivationListener; +import org.eclipse.mylyn.tasks.core.ITaskActivityListener; +import org.eclipse.mylyn.tasks.core.ITaskActivityManager; + +/** + * Manages task elapsed time, scheduling, due dates, and the date ranges + * + * @since 2.1 + * @author Rob Elves + */ +public class TaskActivityManager implements ITaskActivityManager { + + private final TaskActivationHistory taskActivationHistory = new TaskActivationHistory(); + + private final List<ITaskActivityListener> activityListeners = new ArrayList<ITaskActivityListener>(); + + private final List<ITaskActivationListener> activationListeners = new ArrayList<ITaskActivationListener>(); + + private final Set<ITask> allScheduledTasks = new HashSet<ITask>(); + + private final Set<ITask> allDueTasks = new HashSet<ITask>(); + + private final SortedMap<DateRange, Set<ITask>> scheduledTasks = Collections.synchronizedSortedMap(new TreeMap<DateRange, Set<ITask>>()); + + private final SortedMap<Calendar, Set<ITask>> dueTasks = Collections.synchronizedSortedMap(new TreeMap<Calendar, Set<ITask>>()); + + // Map of Calendar (hour) to Tasks active during that hour + private final SortedMap<Calendar, Set<AbstractTask>> activeTasks = Collections.synchronizedSortedMap(new TreeMap<Calendar, Set<AbstractTask>>()); + + // For a given task maps Calendar Hour to duration of time spent (milliseconds) with task active + private final Map<AbstractTask, SortedMap<Calendar, Long>> taskElapsedTimeMap = new ConcurrentHashMap<AbstractTask, SortedMap<Calendar, Long>>(); + + private final Map<String, SortedMap<Calendar, Long>> workingSetElapsedTimeMap = new ConcurrentHashMap<String, SortedMap<Calendar, Long>>(); + + private final TaskList taskList; + + private final TaskRepositoryManager repositoryManager; + + private ITask activeTask; + + private int startDay = Calendar.MONDAY; + + private final ITaskListChangeListener TASKLIST_CHANGE_LISTENER = new ITaskListChangeListener() { + + public void containersChanged(Set<TaskContainerDelta> containers) { + for (TaskContainerDelta taskContainerDelta : containers) { + if (taskContainerDelta.getKind() == TaskContainerDelta.Kind.ROOT) { + reloadPlanningData(); + } + } + } + }; + + public TaskActivityManager(TaskRepositoryManager repositoryManager, TaskList taskList) { + this.taskList = taskList; + this.repositoryManager = repositoryManager; + this.taskList.addChangeListener(TASKLIST_CHANGE_LISTENER); + clear(); + } + + /** + * Get the user specified first day of the week (Calendar.SUNDAY | Calendar.MONDAY) + * + * @see http://en.wikipedia.org/wiki/Days_of_the_week#First_day_of_the_week + */ + public int getWeekStartDay() { + return startDay; + } + + /** + * Set the first day of the week (Calendar.SUNDAY | Calendar.MONDAY) + * + * @see http://en.wikipedia.org/wiki/Days_of_the_week#First_day_of_the_week + * + * @param startDay + * (Calendar.SUNDAY | Calendar.MONDAY) + */ + public void setWeekStartDay(int startDay) { + TaskActivityUtil.setStartDay(startDay); + this.startDay = startDay; + for (ITaskActivityListener listener : activityListeners) { + listener.activityReset(); + } + } + + public void addActivityListener(ITaskActivityListener listener) { + activityListeners.add(listener); + } + + public void removeActivityListener(ITaskActivityListener listener) { + activityListeners.remove(listener); + } + + public void addActivationListener(ITaskActivationListener listener) { + activationListeners.add(listener); + } + + public void removeActivationListener(ITaskActivationListener listener) { + activationListeners.remove(listener); + } + + public void clear() { + dueTasks.clear(); + allDueTasks.clear(); + scheduledTasks.clear(); + allScheduledTasks.clear(); + clearActivity(); + } + + public void clearActivity() { + activeTasks.clear(); + taskActivationHistory.clear(); + taskElapsedTimeMap.clear(); + workingSetElapsedTimeMap.clear(); + } + + public void reloadPlanningData() { + reloadScheduledData(); + for (ITaskActivityListener listener : activityListeners) { + listener.activityReset(); + } + } + + public void removeElapsedTime(ITask task, Date startDate, Date endDate) { + Assert.isNotNull(task); + Assert.isNotNull(startDate); + Assert.isNotNull(endDate); + // remove any time that has already accumulated in data structures + SortedMap<Calendar, Long> activityMap = taskElapsedTimeMap.get(task); + if (activityMap != null) { + Calendar start = TaskActivityUtil.getCalendar(); + start.setTime(startDate); + TaskActivityUtil.snapStartOfHour(start); + Calendar end = TaskActivityUtil.getCalendar(); + end.setTime(endDate); + TaskActivityUtil.snapEndOfHour(end); + activityMap = activityMap.subMap(start, end); + for (Calendar cal : new HashSet<Calendar>(activityMap.keySet())) { + activityMap.remove(cal); + } + for (ITaskActivityListener listener : new ArrayList<ITaskActivityListener>(activityListeners)) { + try { + listener.elapsedTimeUpdated(task, getElapsedTime(task)); + } catch (Throwable t) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Task activity listener failed: \"" + listener + "\"", t)); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + } + + } + + /** + * @param workingSetIds + * working set ids + */ + public void addWorkingSetElapsedTime(String workingSetName, Date startDate, Date endDate) { + Assert.isNotNull(workingSetName); + Assert.isNotNull(startDate); + Assert.isNotNull(endDate); + + long attentionSpan = endDate.getTime() - startDate.getTime(); + + // Ignore any potential negative or zero times + if (attentionSpan <= 0) { + return; + } + + // granularity to the hour + Calendar hourOfDay = TaskActivityUtil.getCalendar(); + hourOfDay.setTime(startDate); + snapToStartOfHour(hourOfDay); + SortedMap<Calendar, Long> noTaskActiveMap = workingSetElapsedTimeMap.get(workingSetName); + if (noTaskActiveMap == null) { + noTaskActiveMap = Collections.synchronizedSortedMap(new TreeMap<Calendar, Long>()); + workingSetElapsedTimeMap.put(workingSetName, noTaskActiveMap); + } + Long daysActivity = noTaskActiveMap.get(hourOfDay); + if (daysActivity == null) { + daysActivity = new Long(0); + } + + daysActivity = daysActivity.longValue() + attentionSpan; + + noTaskActiveMap.put(hourOfDay, daysActivity); + } + + public long getElapsedForWorkingSet(String workingSetId, Calendar startDate, Calendar endDate) { + + Calendar startRange = snapToStartOfHour(getNewInstance(startDate)); + + Calendar endRange = snapToEndOfHour(getNewInstance(endDate)); + + long result = 0; + + SortedMap<Calendar, Long> noTaskActiveMap = workingSetElapsedTimeMap.get(workingSetId); + if (noTaskActiveMap != null) { + + Map<Calendar, Long> subMap = noTaskActiveMap.subMap(startRange, endRange); + for (Long time : subMap.values()) { + if (time != null && time > 0) { + result += time.longValue(); + } + } + } + return result; + } + + public Set<String> getWorkingSets() { + return workingSetElapsedTimeMap.keySet(); + } + + public void addElapsedTime(AbstractTask task, Date startDate, Date endDate) { + Assert.isNotNull(task); + Assert.isNotNull(startDate); + Assert.isNotNull(endDate); + + SortedMap<Calendar, Long> activityMap = taskElapsedTimeMap.get(task); + if (activityMap == null) { + activityMap = Collections.synchronizedSortedMap(new TreeMap<Calendar, Long>()); + taskElapsedTimeMap.put(task, activityMap); + } + + long attentionSpan = endDate.getTime() - startDate.getTime(); + + // Ignore any potential negative or zero times + if (attentionSpan <= 0) { + return; + } + + // granularity to the hour + Calendar hourOfDay = TaskActivityUtil.getCalendar(); + hourOfDay.setTime(startDate); + snapToStartOfHour(hourOfDay); + Long daysActivity = activityMap.get(hourOfDay); + if (daysActivity == null) { + daysActivity = new Long(0); + } + + daysActivity = daysActivity.longValue() + attentionSpan; + + activityMap.put(hourOfDay, daysActivity); + + Set<AbstractTask> active = activeTasks.get(hourOfDay); + if (active == null) { + active = new HashSet<AbstractTask>(); + activeTasks.put(hourOfDay, active); + } + active.add(task); + + long totalElapsed = getElapsedTime(activityMap); + + for (ITaskActivityListener listener : new ArrayList<ITaskActivityListener>(activityListeners)) { + try { + listener.elapsedTimeUpdated(task, totalElapsed); + } catch (Throwable t) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Task activity listener failed: \"" + listener + "\"", t)); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + } + + private Calendar getNewInstance(Calendar cal) { + Calendar newCal = TaskActivityUtil.getCalendar(); + newCal.setTimeInMillis(cal.getTimeInMillis()); + return newCal; + } + + public void addScheduledTask(AbstractTask task) { + DateRange range = task.getScheduledForDate(); + if (range != null) { + Set<ITask> tasks = scheduledTasks.get(range); + if (tasks == null) { + tasks = new CopyOnWriteArraySet<ITask>(); + scheduledTasks.put(range, tasks); + } + tasks.add(task); + allScheduledTasks.add(task); + } else { + removeScheduledTask(task); + } + } + + public void removeScheduledTask(ITask task) { + synchronized (scheduledTasks) { + for (Set<ITask> setOfTasks : scheduledTasks.values()) { + setOfTasks.remove(task); + } + allScheduledTasks.remove(task); + } + } + + public Set<ITask> getScheduledTasks(DateRange range) { + Set<ITask> resultingTasks = new HashSet<ITask>(); + synchronized (scheduledTasks) { + Set<ITask> result = scheduledTasks.get(range); + if (result != null && !result.isEmpty()) { + resultingTasks.addAll(result); + } else if (!(range instanceof WeekDateRange)) { + return getScheduledTasks(range.getStartDate(), range.getEndDate()); + } + if (range instanceof WeekDateRange && TaskActivityUtil.getNextWeek().next().compareTo(range) == 0) { + resultingTasks.addAll(getScheduledTasks(range.getStartDate(), range.getEndDate())); + } + } + return resultingTasks; + } + + public Set<ITask> getScheduledTasks(Calendar start, Calendar end) { + Set<ITask> resultingTasks = new HashSet<ITask>(); + synchronized (scheduledTasks) { + DateRange startRange = new DateRange(start); + Calendar endExclusive = TaskActivityUtil.getCalendar(); + endExclusive.setTimeInMillis(end.getTimeInMillis() + 1); + DateRange endRange = new DateRange(endExclusive); + + SortedMap<DateRange, Set<ITask>> result = scheduledTasks.subMap(startRange, endRange); + for (DateRange range : result.keySet()) { + if (start.compareTo(range.getStartDate()) > 0 || end.compareTo(range.getEndDate()) < 0) { + continue; + } + resultingTasks.addAll(result.get(range)); + } + } + return resultingTasks; + } + + public void addDueTask(ITask task) { + if (task.getDueDate() == null) { + removeDueTask(task); + return; + } + Calendar time = TaskActivityUtil.getCalendar(); + time.setTime(task.getDueDate()); + snapToStartOfHour(time); + synchronized (dueTasks) { + Set<ITask> tasks = dueTasks.get(time); + if (tasks == null) { + tasks = new CopyOnWriteArraySet<ITask>(); + dueTasks.put(time, tasks); + } + tasks.add(task); + allDueTasks.add(task); + } + + } + + public void removeDueTask(ITask task) { + synchronized (dueTasks) { + for (Set<ITask> setOfTasks : dueTasks.values()) { + setOfTasks.remove(task); + } + allDueTasks.remove(task); + } + } + + public Set<ITask> getDueTasks(Calendar start, Calendar end) { + Set<ITask> resultingTasks = new HashSet<ITask>(); + SortedMap<Calendar, Set<ITask>> result = dueTasks.subMap(start, end); + synchronized (dueTasks) { + for (Set<ITask> set : result.values()) { + resultingTasks.addAll(set); + } + } + return resultingTasks; + } + + public void activateTask(ITask task) { + deactivateActiveTask(); + + if (taskList.getTask(task.getRepositoryUrl(), task.getTaskId()) == null) { + taskList.addTask(task, taskList.getDefaultCategory()); + } + + // notify that a task is about to be activated + for (ITaskActivationListener listener : new ArrayList<ITaskActivationListener>(activationListeners)) { + try { + listener.preTaskActivated(task); + } catch (Throwable t) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Task activity listener failed: " + listener, t)); //$NON-NLS-1$ + } + } + + activeTask = task; + ((AbstractTask) activeTask).setActive(true); + + for (ITaskActivationListener listener : new ArrayList<ITaskActivationListener>(activationListeners)) { + try { + listener.taskActivated(task); + } catch (Throwable t) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Task activity listener failed: " + listener, t)); //$NON-NLS-1$ + } + } + } + + public void deactivateActiveTask() { + if (activeTask != null) { + deactivateTask(activeTask); + } + } + + public void deactivateTask(ITask task) { + if (task == null) { + return; + } + + if (task.isActive() && task == activeTask) { + // notify that a task is about to be deactivated + for (ITaskActivationListener listener : new ArrayList<ITaskActivationListener>(activationListeners)) { + try { + listener.preTaskDeactivated(task); + } catch (Throwable t) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Notification failed for: " + listener, t)); //$NON-NLS-1$ + } + } + + ((AbstractTask) activeTask).setActive(false); + activeTask = null; + + for (ITaskActivationListener listener : new ArrayList<ITaskActivationListener>(activationListeners)) { + try { + listener.taskDeactivated(task); + } catch (Throwable t) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Notification failed for: " + listener, t)); //$NON-NLS-1$ + } + } + } else { + ((AbstractTask) task).setActive(false); + } + } + + /** + * returns active tasks from start to end (exclusive) where both are snapped to the beginning of the hour + */ + public Set<AbstractTask> getActiveTasks(Calendar start, Calendar end) { + Set<AbstractTask> resultingTasks = new HashSet<AbstractTask>(); + Calendar startInternal = TaskActivityUtil.getCalendar(); + startInternal.setTimeInMillis(start.getTimeInMillis()); + TaskActivityUtil.snapStartOfHour(startInternal); + + Calendar endInternal = TaskActivityUtil.getCalendar(); + endInternal.setTimeInMillis(end.getTimeInMillis()); + TaskActivityUtil.snapStartOfHour(endInternal); + + synchronized (activeTasks) { + SortedMap<Calendar, Set<AbstractTask>> result = activeTasks.subMap(startInternal, endInternal); + for (Set<AbstractTask> set : result.values()) { + resultingTasks.addAll(set); + } + } + return resultingTasks; + } + + /** total elapsed time based on activation history */ + public long getElapsedTime(ITask task) { + SortedMap<Calendar, Long> activityMap = taskElapsedTimeMap.get(task); + return getElapsedTime(activityMap); + } + + private long getElapsedTime(SortedMap<Calendar, Long> activityMap) { + // TODO: Keep a running total instead of recalculating all the time + long result = 0; + if (activityMap != null) { + synchronized (activityMap) { + for (Long time : activityMap.values()) { + if (time != null) { + result += time.longValue(); + } + } + } + } + return result; + } + + /** + * total elapsed time based on activation history + */ + public long getElapsedTime(ITask task, Calendar start, Calendar end) { + + if (task == null) { + // TODO: return total elapsed with no task active + return 0; + } + + long result = 0; + + Calendar startRange = snapToStartOfHour(getNewInstance(start)); + + Calendar endRange = snapToEndOfHour(getNewInstance(end)); + + SortedMap<Calendar, Long> activityMap = taskElapsedTimeMap.get(task); + if (activityMap != null) { + synchronized (activityMap) { + activityMap = activityMap.subMap(startRange, endRange); + for (Long time : activityMap.values()) { + if (time != null) { + result += time.longValue(); + } + } + } + } + return result; + } + + /** total elapsed time based on activation history */ + public long getElapsedTime(ITask task, DateRange range) { + return getElapsedTime(task, range.getStartDate(), range.getEndDate()); + } + + // TODO: remove, copied from TaskListManager + private Calendar snapToStartOfHour(Calendar cal) { + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.getTime(); + return cal; + } + + // TODO: remove, copied from TaskListManager + private Calendar snapToEndOfHour(Calendar cal) { + cal.set(Calendar.MINUTE, cal.getMaximum(Calendar.MINUTE)); + cal.set(Calendar.SECOND, cal.getMaximum(Calendar.SECOND)); + cal.set(Calendar.MILLISECOND, cal.getMaximum(Calendar.MILLISECOND)); + cal.getTime(); + return cal; + } + + public ITask getActiveTask() { + return activeTask; + } + + private void reloadScheduledData() { + for (AbstractTask task : taskList.getAllTasks()) { + if (task.getScheduledForDate() != null) { + addScheduledTask(task); + } + if (task.getDueDate() != null) { + addDueTask(task); + } + } + } + + public void setScheduledFor(AbstractTask task, DateRange reminderDate) { + Assert.isNotNull(task); + if (reminderDate != null && !reminderDate.equals(task.getScheduledForDate())) { + (task).setReminded(false); + } + + (task).setScheduledForDate(reminderDate); + if (reminderDate == null) { + removeScheduledTask(task); + } else { + removeScheduledTask(task); + addScheduledTask(task); + } + taskList.notifyElementChanged(task); + } + + public void setDueDate(ITask task, Date dueDate) { + task.setDueDate(dueDate); + if (dueDate == null) { + removeDueTask(task); + } else { + removeDueTask(task); + addDueTask(task); + } + taskList.notifyElementChanged(task); + } + + /** + * @return if a repository task, will only return true if the user is a + */ + public boolean isCompletedToday(ITask task) { + if (task != null) { + boolean isOwnedByUser = repositoryManager.isOwnedByUser(task); + if (!isOwnedByUser) { + return false; + } else { + + Date completionDate = task.getCompletionDate(); + if (completionDate != null) { + Calendar completedTime = TaskActivityUtil.getCalendar(); + completedTime.setTime(completionDate); + return TaskActivityUtil.isToday(completedTime); + } + } + } + return false; + } + + public boolean isPastReminder(AbstractTask task) { + if (task == null || task.isCompleted() || task.getScheduledForDate() == null) { + return false; + } else { + return isPastReminder(task.getScheduledForDate(), task.isCompleted()); + } + } + + public boolean isPastReminder(DateRange date, boolean isComplete) { + if (date == null || isComplete) { + return false; + } else { + if (date.getEndDate().compareTo(TaskActivityUtil.getCalendar()) < 0 && date instanceof DayDateRange) { + return true; + } else { + return false; + } + } + } + + public boolean isDueToday(ITask task) { + if (repositoryManager.isOwnedByUser(task) && !task.isCompleted() && task.getDueDate() != null) { + Calendar cal = TaskActivityUtil.getCalendar(); + cal.setTimeInMillis(task.getDueDate().getTime()); + if (TaskActivityUtil.isToday(cal)) { + return true; + } + } + return false; + } + + public boolean isOverdue(ITask task) { + return (!task.isCompleted() && task.getDueDate() != null && new Date().after(task.getDueDate())) + && repositoryManager.isOwnedByUser(task); + } + + public boolean isOwnedByUser(ITask task) { + return repositoryManager.isOwnedByUser(task); + } + + public boolean isActiveThisWeek(ITask task) { + Calendar calStart = TaskActivityUtil.getCalendar(); + TaskActivityUtil.snapStartOfWorkWeek(calStart); + Calendar calEnd = TaskActivityUtil.getCalendar(); + TaskActivityUtil.snapEndOfWeek(calEnd); + return getElapsedTime(task, calStart, calEnd) > 0; + } + + public boolean isScheduledForToday(AbstractTask task) { + if (task != null && task.getScheduledForDate() != null) { + return isScheduledForToday(task.getScheduledForDate()); + } + return false; + } + + public boolean isScheduledForToday(DateRange range) { + if (range != null) { + return TaskActivityUtil.getCurrentWeek().getToday().compareTo(range) == 0; + } + return false; + } + + public boolean isScheduledAfterThisWeek(AbstractTask task) { + if (task != null && task.getScheduledForDate() != null) { + return isScheduledAfterThisWeek(task.getScheduledForDate()); + } + + return false; + } + + public boolean isScheduledAfterThisWeek(DateRange range) { + if (range != null) { + return TaskActivityUtil.isAfterCurrentWeek(range.getStartDate()); + } + return false; + } + + public boolean isScheduledForFuture(AbstractTask task) { + if (task != null && task.getScheduledForDate() != null) { + return isScheduledForFuture(task.getScheduledForDate()); + } + return false; + } + + public boolean isScheduledForFuture(DateRange reminder) { + if (reminder != null) { + return TaskActivityUtil.isFuture(reminder.getStartDate()); + } + return false; + } + + public boolean isScheduledForThisWeek(AbstractTask task) { + boolean result = false; + if (task != null && task.getScheduledForDate() != null) { + result = isScheduledForThisWeek(task.getScheduledForDate()); + } + return result; + } + + public boolean isScheduledForThisWeek(DateRange range) { + if (range != null) { + + return TaskActivityUtil.getCurrentWeek().isCurrentWeekDay(range) + || TaskActivityUtil.getCurrentWeek().compareTo(range) == 0; + } + return false; + } + + public boolean isSheduledForPastWeek(AbstractTask task) { + boolean result = false; + if (task != null && task.getScheduledForDate() != null) { + result = isSheduledForPastWeek(task.getScheduledForDate()); + } + return result; + } + + private boolean isSheduledForPastWeek(DateRange range) { + if (range != null) { + return (range instanceof WeekDateRange && range.isPast()); + } + return false; + } + + public boolean isScheduledForNextWeek(AbstractTask task) { + if (task != null) { + DateRange range = task.getScheduledForDate(); + if (range != null) { + return TaskActivityUtil.isNextWeek(range.getStartDate()); + } + } + return false; + } + + public void scheduleNewTask(AbstractTask newTask) { + newTask.setCreationDate(new Date()); + // TODO: set based on preference? see bug#158461 + setScheduledFor(newTask, TaskActivityUtil.getCurrentWeek()); + } + + public boolean isDueThisWeek(ITask task) { + Date due = task.getDueDate(); + if (due != null && repositoryManager.isOwnedByUser(task)) { + Calendar cal = TaskActivityUtil.getCalendar(); + cal.setTime(due); + return TaskActivityUtil.isThisWeek(cal); + } + return false; + } + + /** + * Note: Returns all task scheduled for a SPECIFIC day this week. Not those in the "This Week" / Someday bin + */ + public Set<ITask> getScheduledForADayThisWeek() { + DateRange current = TaskActivityUtil.getCurrentWeek(); + return getScheduledTasks(current.getStartDate(), current.getEndDate()); + } + + public TaskActivationHistory getTaskActivationHistory() { + return taskActivationHistory; + } + + public Set<ITask> getAllScheduledTasks() { + return new HashSet<ITask>(allScheduledTasks); + } + + public Set<AbstractTask> getAllScheduledTasksInternal() { + Set<AbstractTask> tasks = new HashSet<AbstractTask>(); + synchronized (scheduledTasks) { + for (ITask task : allScheduledTasks) { + if (task instanceof AbstractTask) { + tasks.add((AbstractTask) task); + } + } + } + return tasks; + } + + public Set<ITask> getAllDueTasks() { + return new HashSet<ITask>(allDueTasks); + } + + public Set<ITask> getOverScheduledTasks() { + Set<ITask> children = new HashSet<ITask>(); + Calendar start = TaskActivityUtil.getCalendar(); + start.setTimeInMillis(0); + Calendar end = TaskActivityUtil.getCalendar(); + TaskActivityUtil.snapStartOfDay(end); + for (ITask task : getScheduledTasks(start, end)) { + if (!task.isCompleted()) { + children.add(task); + } + } + return children; + + } + + public Collection<? extends ITask> getOverDueTasks() { + Set<ITask> children = new HashSet<ITask>(); + Calendar start = TaskActivityUtil.getCalendar(); + start.setTimeInMillis(0); + Calendar end = TaskActivityUtil.getCalendar(); + TaskActivityUtil.snapStartOfHour(end); + for (ITask task : getDueTasks(start, end)) { + if (!task.isCompleted() && repositoryManager.isOwnedByUser(task)) { + children.add(task); + } + } + return children; + } + + public Collection<AbstractTask> getUnscheduled() { + Set<AbstractTask> allTasks = new HashSet<AbstractTask>(taskList.getAllTasks()); + for (ITask abstractTask : getAllScheduledTasks()) { + allTasks.remove(abstractTask); + } + return allTasks; + } + + public boolean isActive(ITask task) { + Assert.isNotNull(task); + return task.equals(getActiveTask()); + } +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivityUtil.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivityUtil.java new file mode 100644 index 000000000..b69923f11 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivityUtil.java @@ -0,0 +1,263 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.Calendar; +import java.util.Date; + +/** + * @author Rob Elves + */ +public class TaskActivityUtil { + + private static int startDay = Calendar.MONDAY; + + private static int endHour = 17; + + public static Calendar snapStartOfDay(Calendar cal) { + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.getTime(); + return cal; + } + + public static Calendar snapStartOfHour(Calendar cal) { + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.getTime(); + return cal; + } + + public static Calendar snapEndOfHour(Calendar cal) { + cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE)); + cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND)); + cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND)); + cal.getTime(); + return cal; + } + + public static Calendar snapEndOfDay(Calendar cal) { + cal.set(Calendar.HOUR_OF_DAY, cal.getActualMaximum(Calendar.HOUR_OF_DAY)); + cal.set(Calendar.MINUTE, cal.getActualMaximum(Calendar.MINUTE)); + cal.set(Calendar.SECOND, cal.getActualMaximum(Calendar.SECOND)); + cal.set(Calendar.MILLISECOND, cal.getActualMaximum(Calendar.MILLISECOND)); + cal.getTime(); + return cal; + } + + public static void snapToNextDay(Calendar cal) { + cal.add(Calendar.DAY_OF_MONTH, 1); + TaskActivityUtil.snapStartOfDay(cal); + } + + public static Calendar snapNextDay(Calendar cal) { + cal.add(Calendar.DAY_OF_MONTH, 1); + snapStartOfDay(cal); + return cal; + } + + public static Calendar snapStartOfWorkWeek(Calendar cal) { + cal.set(Calendar.DAY_OF_WEEK, startDay); + snapStartOfDay(cal); + return cal; + } + + public static Calendar snapEndOfWeek(Calendar cal) { + + cal.set(Calendar.DAY_OF_WEEK, getLastCalDayInt(cal)); + + snapEndOfDay(cal); + return cal; + } + + private static int getLastCalDayInt(Calendar cal) { + int last = cal.getFirstDayOfWeek() - 1; + + if (last == 0) { + last = Calendar.SATURDAY; + } + + return last; + } + + public static Calendar snapEndOfNextWeek(Calendar cal) { + snapEndOfWeek(cal); + cal.add(Calendar.WEEK_OF_MONTH, 1); + return cal; + } + + public static Calendar snapForwardNumDays(Calendar calendar, int days) { + calendar.add(Calendar.DAY_OF_MONTH, days); + calendar.set(Calendar.HOUR_OF_DAY, endHour); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar; + } + + public static Calendar snapEndOfWorkDay(Calendar calendar) { + calendar.set(Calendar.HOUR_OF_DAY, endHour); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar; + } + + public static Calendar snapNextWorkWeek(Calendar calendar) { + calendar.add(Calendar.WEEK_OF_MONTH, 1); + snapStartOfWorkWeek(calendar); + return calendar; + } + + public static boolean isAfterCurrentWeek(Calendar time) { + if (time != null) { + Calendar cal = getCalendar(); + return time.compareTo(snapNextWorkWeek(cal)) > -1; + } + return false; + } + + /** + * @return true if time is in or past Future bin + */ + public static boolean isFuture(Calendar time) { + if (time != null) { + Calendar cal = getCalendar(); + cal.add(Calendar.WEEK_OF_MONTH, 2); + snapStartOfWorkWeek(cal); + return time.compareTo(cal) > -1; + } + return false; + } + + public static boolean isThisWeek(Calendar time) { + if (time != null) { + Calendar weekStart = getCalendar(); + snapStartOfWorkWeek(weekStart); + Calendar weekEnd = getCalendar(); + snapEndOfWeek(weekEnd); + return (time.compareTo(weekStart) >= 0 && time.compareTo(weekEnd) <= 0); + } + return false; + } + + public static boolean isNextWeek(Calendar time) { + if (time != null) { + Calendar weekStart = getCalendar(); + snapNextWorkWeek(weekStart); + Calendar weekEnd = getCalendar(); + snapNextWorkWeek(weekEnd); + snapEndOfWeek(weekEnd); + return (time.compareTo(weekStart) >= 0 && time.compareTo(weekEnd) <= 0); + } + return false; + } + + public static boolean isToday(Calendar time) { + if (time != null) { + Calendar dayStart = getCalendar(); + snapStartOfDay(dayStart); + Calendar midnight = getCalendar(); + snapEndOfDay(midnight); + return (time.compareTo(dayStart) >= 0 && time.compareTo(midnight) <= 0); + } + return false; + } + + public static boolean isToday(DateRange time) { + if (time != null) { + return getCurrentWeek().getToday().compareTo(time) == 0; + } + return false; + } + + public static Calendar getCalendar() { + Calendar cal = Calendar.getInstance(); + cal.setFirstDayOfWeek(startDay); + cal.getTime(); + return cal; + } + + public static Calendar getStartOfCurrentWeek() { + Calendar cal = getCalendar(); + return snapStartOfWorkWeek(cal); + } + + public static Calendar getStartOfNextWeek() { + Calendar cal = getCalendar(); + snapNextWorkWeek(cal); + return snapStartOfWorkWeek(cal); + } + + public static Calendar getEndOfCurrentWeek() { + Calendar cal = getCalendar(); + return snapEndOfWeek(cal); + } + + public static boolean isBetween(Calendar time, Calendar start, Calendar end) { + return (time.compareTo(start) >= 0 && time.compareTo(end) <= 0); + } + + protected static void setStartDay(int startDay) { + TaskActivityUtil.startDay = startDay; + } + + protected static int getStartDay() { + return TaskActivityUtil.startDay; + } + + public static void setEndHour(int endHour) { + TaskActivityUtil.endHour = endHour; + } + + public static WeekDateRange getCurrentWeek() { + Calendar weekStart = getCalendar(); + snapStartOfWorkWeek(weekStart); + Calendar weekEnd = getCalendar(); + snapEndOfWeek(weekEnd); + return new WeekDateRange(weekStart, weekEnd); + } + + public static WeekDateRange getNextWeek() { + Calendar weekStart = getCalendar(); + snapNextWorkWeek(weekStart); + Calendar weekEnd = getCalendar(); + weekEnd.setTimeInMillis(weekStart.getTimeInMillis()); + snapEndOfWeek(weekEnd); + return new WeekDateRange(weekStart, weekEnd); + } + + public static WeekDateRange getWeekOf(Date date) { + Calendar weekStart = getCalendar(); + weekStart.setTime(date); + Calendar weekEnd = getCalendar(); + weekEnd.setTime(date); + + snapStartOfWorkWeek(weekStart); + snapEndOfWeek(weekEnd); + return new WeekDateRange(weekStart, weekEnd); + } + + public static DayDateRange getDayOf(Date date) { + Calendar dayStart = getCalendar(); + dayStart.setTime(date); + Calendar dayEnd = getCalendar(); + dayEnd.setTime(date); + + snapStartOfDay(dayStart); + snapEndOfDay(dayEnd); + return new DayDateRange(dayStart, dayEnd); + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskAttachment.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskAttachment.java new file mode 100644 index 000000000..b956e9d41 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskAttachment.java @@ -0,0 +1,163 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.Date; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.mylyn.tasks.core.IRepositoryPerson; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITaskAttachment; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; + +/** + * @author Steffen Pingel + */ +public class TaskAttachment implements ITaskAttachment { + + private IRepositoryPerson author; + + private String comment; + + private String contentType; + + private Date creationDate; + + private boolean deprecated; + + private String description; + + private String fileName; + + private long length; + + private boolean patch; + + private final ITask task; + + private final TaskAttribute taskAttribute; + + private final TaskRepository taskRepository; + + private String url; + + public TaskAttachment(TaskRepository taskRepository, ITask task, TaskAttribute taskAttribute) { + Assert.isNotNull(taskRepository); + Assert.isNotNull(task); + Assert.isNotNull(taskAttribute); + this.taskRepository = taskRepository; + this.task = task; + this.taskAttribute = taskAttribute; + } + + public IRepositoryPerson getAuthor() { + return author; + } + + public String getComment() { + return comment; + } + + public String getConnectorKind() { + return taskRepository.getConnectorKind(); + } + + public String getContentType() { + return contentType; + } + + public Date getCreationDate() { + return creationDate; + } + + public String getDescription() { + return description; + } + + public String getFileName() { + return fileName; + } + + public long getLength() { + return length; + } + + public String getRepositoryUrl() { + return taskRepository.getRepositoryUrl(); + } + + public ITask getTask() { + return task; + } + + public TaskAttribute getTaskAttribute() { + return taskAttribute; + } + + public TaskRepository getTaskRepository() { + return taskRepository; + } + + public String getUrl() { + return url; + } + + public boolean isDeprecated() { + return deprecated; + } + + public boolean isPatch() { + return patch; + } + + public void setAuthor(IRepositoryPerson author) { + this.author = author; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public void setDeprecated(boolean deprecated) { + this.deprecated = deprecated; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public void setLength(long length) { + this.length = length; + } + + public void setPatch(boolean patch) { + this.patch = patch; + } + + public void setUrl(String url) { + this.url = url; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskCategory.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskCategory.java new file mode 100644 index 000000000..9fddb0355 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskCategory.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITaskContainer; + +/** + * @author Mik Kersten + */ +public final class TaskCategory extends AbstractTaskCategory { + + private String summary; + + public TaskCategory(String handle, String summary) { + super(handle); + setSummary(summary); + } + + public TaskCategory(String handleAndDescription) { + this(handleAndDescription, handleAndDescription); + } + + /** + * null if no parent category + */ + public static AbstractTaskCategory getParentTaskCategory(ITask task) { + AbstractTaskCategory category = null; + if (task != null) { + for (ITaskContainer container : ((AbstractTask) task).getParentContainers()) { + if (container instanceof AbstractTaskCategory) { + category = (AbstractTaskCategory) container; + } + } + } + return category; + } + + @Override + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskComment.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskComment.java new file mode 100644 index 000000000..a9fdc37c4 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskComment.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.Date; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.mylyn.tasks.core.IRepositoryPerson; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITaskComment; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; + +/** + * A comment posted by a user on a task. + * + * @author Steffen Pingel + */ +public class TaskComment implements ITaskComment { + + private IRepositoryPerson author; + + private Date creationDate; + + private int number; + + private final ITask task; + + private final TaskAttribute taskAttribute; + + private final TaskRepository taskRepository; + + private String text; + + private String url; + + public TaskComment(TaskRepository taskRepository, ITask task, TaskAttribute taskAttribute) { + Assert.isNotNull(taskRepository); + Assert.isNotNull(task); + Assert.isNotNull(taskAttribute); + this.taskRepository = taskRepository; + this.task = task; + this.taskAttribute = taskAttribute; + } + + public IRepositoryPerson getAuthor() { + return author; + } + + public String getConnectorKind() { + return taskRepository.getConnectorKind(); + } + + public Date getCreationDate() { + return creationDate; + } + + public int getNumber() { + return number; + } + + public String getRepositoryUrl() { + return taskRepository.getRepositoryUrl(); + } + + public ITask getTask() { + return task; + } + + public TaskAttribute getTaskAttribute() { + return taskAttribute; + } + + public TaskRepository getTaskRepository() { + return taskRepository; + } + + public String getText() { + return text; + } + + public String getUrl() { + return url; + } + + public void setAuthor(IRepositoryPerson author) { + this.author = author; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public void setNumber(int number) { + this.number = number; + } + + public void setText(String text) { + this.text = text; + } + + public void setUrl(String url) { + this.url = url; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskContainerDelta.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskContainerDelta.java new file mode 100644 index 000000000..91a1cdc48 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskContainerDelta.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import org.eclipse.mylyn.tasks.core.IRepositoryElement; +import org.eclipse.mylyn.tasks.core.ITaskContainer; + +/** + * Immutable. Defines changes to Task List elements. + * + * @author Mik Kersten + * @since 2.0 + */ +public final class TaskContainerDelta { + + public enum Kind { + /** + * One container (source) added to another (target) + */ + ADDED, + + /** + * One container (source) removed from another (target) + */ + REMOVED, + + /** + * The internal state of the container (target) has changed, e.g. attributes, summary, priority, etc + */ + CONTENT, + + /** + * The element has been deleted from the tasklist + */ + DELETED, + + /** + * The root of the data structure has changed. + */ + ROOT + } + + private final ITaskContainer parent; + + private final IRepositoryElement element; + + private final Kind kind; + + private boolean isTransient; + + /** + * @param element + * - object being moved/added/removed, source assumed to be root + * @since 3.0 + */ + public TaskContainerDelta(IRepositoryElement element, Kind kind) { + this.element = element; + this.parent = null; + this.kind = kind; + } + + /** + * @since 3.0 + */ + public TaskContainerDelta(IRepositoryElement element, ITaskContainer parent, Kind kind) { + this.element = element; + this.parent = parent; + this.kind = kind; + } + + /** + * The <code>target</code> is the container that the <code>source</code> is being moved from/to + * + * @since 3.0 + */ + public ITaskContainer getParent() { + return parent; + } + + /** + * The element being ADDED or REMOVED wrt the <code>target</code> + * + * @since 3.0 + */ + public IRepositoryElement getElement() { + return element; + } + + public Kind getKind() { + return kind; + } + + /** + * @since 3.0 + */ + public void setTransient(boolean isTransient) { + this.isTransient = isTransient; + } + + /** + * @since 3.0 + */ + public boolean isTransient() { + return isTransient; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskExternalizationException.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskExternalizationException.java new file mode 100644 index 000000000..3070c5d44 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskExternalizationException.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + * Ken Sueda - improvements + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +/** + * @author Mik Kersten + * @author Ken Sueda + */ +public class TaskExternalizationException extends Exception { + + private static final long serialVersionUID = 5804522104992031907L; + + public TaskExternalizationException() { + super(); + } + + public TaskExternalizationException(String detailMessage) { + super(detailMessage); + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskGroup.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskGroup.java new file mode 100644 index 000000000..b62c057ea --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskGroup.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Eugen Kuleshov - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +/** + * NOTE: this class is likely to change or become API for 3.0 + * + * @author Eugene Kuleshov + * @since 2.1 + */ +public class TaskGroup extends AbstractTaskContainer { + + private final String summary; + + private final String groupBy; + + public TaskGroup(String parentHandle, String summary, String groupBy) { + super(parentHandle + summary); + this.summary = summary; + this.groupBy = groupBy; + } + + @Override + public String getSummary() { + return summary; + } + + @Override + public boolean isUserManaged() { + return false; + } + + public String getGroupBy() { + return groupBy; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskList.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskList.java new file mode 100644 index 000000000..039260b73 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskList.java @@ -0,0 +1,723 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.ILock; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.commons.net.Policy; +import org.eclipse.mylyn.tasks.core.IRepositoryElement; +import org.eclipse.mylyn.tasks.core.IRepositoryQuery; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITask.SynchronizationState; + +/** + * Stores and manages task list elements and their containment hierarchy. + * + * @author Mik Kersten + * @author Rob Elves + * @since 3.0 + */ +public class TaskList implements ITaskList, ITransferList { + + private static String DEFAULT_HANDLE_PREFIX = "handle-"; //$NON-NLS-1$ + + private static ILock lock = Job.getJobManager().newLock(); + + private Map<String, AbstractTaskCategory> categories; + + private final Set<ITaskListChangeListener> changeListeners = new CopyOnWriteArraySet<ITaskListChangeListener>(); + + private UncategorizedTaskContainer defaultCategory; + + private int maxLocalTaskId; + + private Map<String, RepositoryQuery> queries; + + private Map<String, UnmatchedTaskContainer> repositoryOrphansMap; + + private Map<String, UnsubmittedTaskContainer> unsubmittedTasksMap; + + private Map<String, AbstractTask> tasks; + + private Set<TaskContainerDelta> delta; + + private int nextHandle = 1; + + public TaskList() { + reset(); + } + + public void addCategory(TaskCategory category) { + Assert.isNotNull(category); + try { + lock(); + if (categories.containsKey(category.getHandleIdentifier())) { + throw new IllegalArgumentException("Handle " + category.getHandleIdentifier() //$NON-NLS-1$ + + " already exists in task list"); //$NON-NLS-1$ + } + categories.put(category.getHandleIdentifier(), category); + delta.add(new TaskContainerDelta(category, TaskContainerDelta.Kind.ADDED)); + } finally { + unlock(); + } + } + + public void addChangeListener(ITaskListChangeListener listener) { + changeListeners.add(listener); + } + + /** + * precondition: task must not be null and must exist in the task list + */ + private void addOrphan(AbstractTask task, Set<TaskContainerDelta> delta) { + if (!task.getParentContainers().isEmpty()) { + // Current policy is not to archive/orphan if the task exists in some other container + return; + } + + AbstractTaskContainer orphans = getUnmatchedContainer(task.getRepositoryUrl()); + if (orphans != null) { + task.addParentContainer(orphans); + orphans.internalAddChild(task); + delta.add(new TaskContainerDelta(task, orphans, TaskContainerDelta.Kind.ADDED)); + } + } + + public void addQuery(RepositoryQuery query) throws IllegalArgumentException { + Assert.isNotNull(query); + try { + lock(); + if (queries.containsKey(query.getHandleIdentifier())) { + throw new IllegalArgumentException("Handle " + query.getHandleIdentifier() //$NON-NLS-1$ + + " already exists in task list"); //$NON-NLS-1$ + } + queries.put(query.getHandleIdentifier(), query); + delta.add(new TaskContainerDelta(query, TaskContainerDelta.Kind.ADDED)); + } finally { + unlock(); + } + } + + /** + * Add orphaned task to the task list + */ + public void addTask(ITask task) { + addTask(task, null); + } + + public boolean addTask(ITask itask, AbstractTaskContainer container) { + AbstractTask task = (AbstractTask) itask; + Assert.isNotNull(task); + Assert.isLegal(!(container instanceof UnmatchedTaskContainer)); + + try { + lock(); + task = getOrCreateTask(task); + if (task.getSynchronizationState() == SynchronizationState.OUTGOING_NEW) { + String repositoryUrl = task.getAttribute(ITasksCoreConstants.ATTRIBUTE_OUTGOING_NEW_REPOSITORY_URL); + if (repositoryUrl != null) { + container = getUnsubmittedContainer(repositoryUrl); + } + } + if (container == null) { + container = getUnmatchedContainer(task.getRepositoryUrl()); + } else { + container = getValidElement(container); + } + + if (container instanceof UnsubmittedTaskContainer && container.isEmpty()) { + delta.add(new TaskContainerDelta(container, TaskContainerDelta.Kind.ROOT)); + } + + // ensure parent is valid and does not contain task already + if (container == null || task.equals(container) || task.getParentContainers().contains(container)) { + return false; + } + + // ensure that we don't create cycles + if ((task).contains(container.getHandleIdentifier())) { + return false; + } + + if (task instanceof LocalTask && task.getParentContainers().size() > 0) { + // local tasks should only have 1 parent + for (AbstractTaskContainer parent : task.getParentContainers()) { + removeFromContainerInternal(parent, task, delta); + } + } else if (container instanceof AbstractTaskCategory) { + // tasks can only be in one task category at a time + AbstractTaskCategory tempCat = TaskCategory.getParentTaskCategory(task); + if (tempCat != null) { + removeFromContainerInternal(tempCat, task, delta); + } + } + + removeOrphan(task, delta); + + (task).addParentContainer(container); + container.internalAddChild(task); + delta.add(new TaskContainerDelta(task, container, TaskContainerDelta.Kind.ADDED)); + } finally { + unlock(); + } + + return true; + } + + public void addUnmatchedContainer(UnmatchedTaskContainer orphanedTasksContainer) { + repositoryOrphansMap.put(orphanedTasksContainer.getRepositoryUrl(), orphanedTasksContainer); + unsubmittedTasksMap.put(orphanedTasksContainer.getRepositoryUrl(), new UnsubmittedTaskContainer( + orphanedTasksContainer.getConnectorKind(), orphanedTasksContainer.getRepositoryUrl())); + } + + public void deleteCategory(AbstractTaskCategory category) { + try { + lock(); + categories.remove(category.getHandleIdentifier()); + for (ITask task : category.getChildren()) { + ((AbstractTask) task).removeParentContainer(category); + addOrphan((AbstractTask) task, delta); + } + delta.add(new TaskContainerDelta(category, TaskContainerDelta.Kind.REMOVED)); + delta.add(new TaskContainerDelta(category, TaskContainerDelta.Kind.DELETED)); + } finally { + unlock(); + } + } + + public void deleteQuery(RepositoryQuery query) { + try { + lock(); + queries.remove(query.getHandleIdentifier()); + for (ITask task : query.getChildren()) { + ((AbstractTask) task).removeParentContainer(query); + addOrphan((AbstractTask) task, delta); + } + delta.add(new TaskContainerDelta(query, TaskContainerDelta.Kind.REMOVED)); + delta.add(new TaskContainerDelta(query, TaskContainerDelta.Kind.DELETED)); + } finally { + unlock(); + } + } + + /** + * Task is removed from all containers. Currently subtasks are not deleted but rather are rather potentially + * orphaned. + */ + public void deleteTask(ITask itask) { + Assert.isNotNull(itask); + AbstractTask task = (AbstractTask) itask; + try { + lock(); + + // remove task from all parent containers + for (AbstractTaskContainer container : task.getParentContainers()) { + removeFromContainerInternal(container, task, delta); + } + + // remove this task as a parent for all subtasks + for (ITask child : task.getChildren()) { + removeFromContainerInternal(task, child, delta); + addOrphan((AbstractTask) child, delta); + } + + tasks.remove(task.getHandleIdentifier()); + delta.add(new TaskContainerDelta(task, TaskContainerDelta.Kind.REMOVED)); + delta.add(new TaskContainerDelta(task, TaskContainerDelta.Kind.DELETED)); + } finally { + unlock(); + } + } + + private void fireDelta(HashSet<TaskContainerDelta> deltasToFire) { + for (ITaskListChangeListener listener : changeListeners) { + try { + listener.containersChanged(Collections.unmodifiableSet(deltasToFire)); + } catch (Throwable t) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Notification failed for: " //$NON-NLS-1$ + + listener, t)); + } + } + } + + public Collection<AbstractTask> getAllTasks() { + return Collections.unmodifiableCollection(tasks.values()); + } + + public Set<AbstractTaskCategory> getCategories() { + return Collections.unmodifiableSet(new HashSet<AbstractTaskCategory>(categories.values())); + } + + /** + * Exposed for unit testing + * + * @return unmodifiable collection of ITaskActivityListeners + */ + public Set<ITaskListChangeListener> getChangeListeners() { + return Collections.unmodifiableSet(changeListeners); + } + + public AbstractTaskCategory getContainerForHandle(String categoryHandle) { + Assert.isNotNull(categoryHandle); + for (AbstractTaskCategory cat : categories.values()) { + if (cat.getHandleIdentifier().equals(categoryHandle)) { + return cat; + } + } + return null; + } + + public AbstractTaskCategory getDefaultCategory() { + return defaultCategory; + } + + public int getLastLocalTaskId() { + return maxLocalTaskId; + } + + public int getNextLocalTaskId() { + try { + lock(); + return ++maxLocalTaskId; + } finally { + unlock(); + } + } + + private AbstractTask getOrCreateTask(AbstractTask taskListElement) { + AbstractTask task = tasks.get(taskListElement.getHandleIdentifier()); + if (task == null) { + tasks.put(taskListElement.getHandleIdentifier(), taskListElement); + task = taskListElement; + if (task instanceof LocalTask) { + try { + int taskId = Integer.parseInt(task.getTaskId()); + maxLocalTaskId = Math.max(maxLocalTaskId, taskId); + } catch (NumberFormatException e) { + // ignore + } + } + } + return task; + } + + public Set<RepositoryQuery> getQueries() { + return Collections.unmodifiableSet(new HashSet<RepositoryQuery>(queries.values())); + } + + /** + * return all queries for the given repository url + */ + public Set<RepositoryQuery> getRepositoryQueries(String repositoryUrl) { + Assert.isNotNull(repositoryUrl); + + Set<RepositoryQuery> repositoryQueries = new HashSet<RepositoryQuery>(); + for (RepositoryQuery query : queries.values()) { + if (query.getRepositoryUrl().equals(repositoryUrl)) { + repositoryQueries.add(query); + } + } + return repositoryQueries; + } + + public Set<AbstractTaskContainer> getRootElements() { + Set<AbstractTaskContainer> roots = new HashSet<AbstractTaskContainer>(); + roots.add(defaultCategory); + for (AbstractTaskCategory cat : categories.values()) { + roots.add(cat); + } + for (RepositoryQuery query : queries.values()) { + roots.add(query); + } + for (UnmatchedTaskContainer orphanContainer : repositoryOrphansMap.values()) { + roots.add(orphanContainer); + } + for (UnsubmittedTaskContainer unsubmitedTaskContainer : unsubmittedTasksMap.values()) { + roots.add(unsubmitedTaskContainer); + } + return roots; + } + + /** + * TODO: consider removing, if everything becomes a repository task + * + * @return null if no such task. + */ + public AbstractTask getTask(String handleIdentifier) { + if (handleIdentifier == null) { + return null; + } else { + return tasks.get(handleIdentifier); + } + } + + public ITask getTask(String repositoryUrl, String taskId) { + if (!RepositoryTaskHandleUtil.isValidTaskId(taskId)) { + return null; + } + + String handle = RepositoryTaskHandleUtil.getHandle(repositoryUrl, taskId); + return getTask(handle); + } + + public AbstractTask getTaskByKey(String repositoryUrl, String taskKey) { + for (AbstractTask task : tasks.values()) { + String currentTaskKey = task.getTaskKey(); + if (currentTaskKey != null && currentTaskKey.equals(taskKey) + && task.getRepositoryUrl().equals(repositoryUrl)) { + return task; + } + } + return null; + } + + public Set<AbstractTaskCategory> getTaskCategories() { + Set<AbstractTaskCategory> containers = new HashSet<AbstractTaskCategory>(); + for (AbstractTaskCategory container : categories.values()) { + if (container instanceof TaskCategory) { + containers.add(container); + } + } + return containers; + } + + /** + * Returns all tasks for the given repository url. + */ + public Set<ITask> getTasks(String repositoryUrl) { + Set<ITask> repositoryTasks = new HashSet<ITask>(); + if (repositoryUrl != null) { + for (ITask task : tasks.values()) { + if (task.getRepositoryUrl().equals(repositoryUrl)) { + repositoryTasks.add(task); + } + } + } + return repositoryTasks; + } + + public AbstractTaskContainer getUnmatchedContainer(String repositoryUrl) { + if (LocalRepositoryConnector.REPOSITORY_URL.equals(repositoryUrl)) { + return defaultCategory; + } else { + UnmatchedTaskContainer orphans = repositoryOrphansMap.get(repositoryUrl); + if (orphans == null) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Failed to find unmatched container for repository \"" + repositoryUrl + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ + } + return orphans; + } + } + + public Set<UnmatchedTaskContainer> getUnmatchedContainers() { + return Collections.unmodifiableSet(new HashSet<UnmatchedTaskContainer>(repositoryOrphansMap.values())); + } + + /** + * Task added if does not exist already. Ensures the element exists in the task list + * + * @throws IllegalAgumentException + * if null argument passed or element does not exist in task list + * @return element as passed in or instance from task list with same handle if exists + */ + private AbstractTaskContainer getValidElement(IRepositoryElement taskListElement) { + AbstractTaskContainer result = null; + if (taskListElement instanceof ITask) { + result = tasks.get(taskListElement.getHandleIdentifier()); + } else if (taskListElement instanceof UncategorizedTaskContainer) { + result = defaultCategory; + } else if (taskListElement instanceof UnmatchedTaskContainer) { + result = repositoryOrphansMap.get(((UnmatchedTaskContainer) taskListElement).getRepositoryUrl()); + } else if (taskListElement instanceof UnsubmittedTaskContainer) { + result = unsubmittedTasksMap.get(((UnsubmittedTaskContainer) taskListElement).getRepositoryUrl()); + } else if (taskListElement instanceof TaskCategory) { + result = categories.get(taskListElement.getHandleIdentifier()); + } else if (taskListElement instanceof IRepositoryQuery) { + result = queries.get(taskListElement.getHandleIdentifier()); + } + + if (result == null) { + throw new IllegalArgumentException("Element " + taskListElement.getHandleIdentifier() //$NON-NLS-1$ + + " does not exist in the task list."); //$NON-NLS-1$ + } else { + return result; + } + } + + public void notifyElementsChanged(Set<? extends IRepositoryElement> elements) { + HashSet<TaskContainerDelta> deltas = new HashSet<TaskContainerDelta>(); + if (elements == null) { + deltas.add(new TaskContainerDelta(null, TaskContainerDelta.Kind.ROOT)); + } else { + for (IRepositoryElement element : elements) { + deltas.add(new TaskContainerDelta(element, TaskContainerDelta.Kind.CONTENT)); + } + } + + fireDelta(deltas); + } + + // TODO rename: this indicates a change of the synchronizing/status flag, not of the synchronization state + public void notifySynchronizationStateChanged(Set<? extends IRepositoryElement> elements) { + HashSet<TaskContainerDelta> taskChangeDeltas = new HashSet<TaskContainerDelta>(); + for (IRepositoryElement abstractTaskContainer : elements) { + TaskContainerDelta delta = new TaskContainerDelta(abstractTaskContainer, TaskContainerDelta.Kind.CONTENT); + delta.setTransient(true); + taskChangeDeltas.add(delta); + } + + fireDelta(taskChangeDeltas); + } + + // TODO rename: this indicates a change of the synchronizing/status flag, not of the synchronization state + public void notifySynchronizationStateChanged(IRepositoryElement element) { + notifySynchronizationStateChanged(Collections.singleton(element)); + } + + public void notifyElementChanged(IRepositoryElement element) { + notifyElementsChanged(Collections.singleton(element)); + } + + public void refactorRepositoryUrl(String oldRepositoryUrl, String newRepositoryUrl) { + Assert.isNotNull(oldRepositoryUrl); + Assert.isNotNull(newRepositoryUrl); + + try { + lock(); + for (AbstractTask task : tasks.values()) { + if (oldRepositoryUrl.equals(RepositoryTaskHandleUtil.getRepositoryUrl(task.getHandleIdentifier()))) { + tasks.remove(task.getHandleIdentifier()); + task.setRepositoryUrl(newRepositoryUrl); + tasks.put(task.getHandleIdentifier(), task); + String taskUrl = task.getUrl(); + if (taskUrl != null && taskUrl.startsWith(oldRepositoryUrl)) { + task.setUrl(newRepositoryUrl + taskUrl.substring(oldRepositoryUrl.length())); + } + } + if (oldRepositoryUrl.equals(task.getAttribute(ITasksCoreConstants.ATTRIBUTE_OUTGOING_NEW_REPOSITORY_URL))) { + task.setAttribute(ITasksCoreConstants.ATTRIBUTE_OUTGOING_NEW_REPOSITORY_URL, newRepositoryUrl); + } + } + + for (RepositoryQuery query : queries.values()) { + if (query.getRepositoryUrl().equals(oldRepositoryUrl)) { + query.setRepositoryUrl(newRepositoryUrl); + delta.add(new TaskContainerDelta(query, TaskContainerDelta.Kind.CONTENT)); + } + } + + for (UnmatchedTaskContainer orphans : repositoryOrphansMap.values()) { + if (orphans.getRepositoryUrl().equals(oldRepositoryUrl)) { + repositoryOrphansMap.remove(oldRepositoryUrl); + //categories.remove(orphans.getHandleIdentifier()); + orphans.setRepositoryUrl(newRepositoryUrl); + repositoryOrphansMap.put(newRepositoryUrl, orphans); + //categories.put(orphans.getHandleIdentifier(), orphans); + delta.add(new TaskContainerDelta(orphans, TaskContainerDelta.Kind.CONTENT)); + } + } + for (UnsubmittedTaskContainer unsubmitted : unsubmittedTasksMap.values()) { + if (unsubmitted.getRepositoryUrl().equals(oldRepositoryUrl)) { + unsubmittedTasksMap.remove(oldRepositoryUrl); + unsubmitted.setRepositoryUrl(newRepositoryUrl); + unsubmittedTasksMap.put(newRepositoryUrl, unsubmitted); + delta.add(new TaskContainerDelta(unsubmitted, TaskContainerDelta.Kind.CONTENT)); + } + } + } finally { + unlock(); + } + } + + public void removeChangeListener(ITaskListChangeListener listener) { + changeListeners.remove(listener); + } + + public void removeFromContainer(AbstractTaskContainer container, ITask task) { + Assert.isNotNull(container); + Assert.isNotNull(task); + + removeFromContainer(container, Collections.singleton(task)); + } + + public void removeFromContainer(AbstractTaskContainer container, Set<ITask> tasks) { + Assert.isNotNull(container); + Assert.isNotNull(tasks); + try { + lock(); + for (ITask task : tasks) { + removeFromContainerInternal(container, task, delta); + addOrphan((AbstractTask) task, delta); + } + } finally { + unlock(); + } + } + + /** + * Note: does not add <code>task</code> to the unmatched container. + */ + private void removeFromContainerInternal(AbstractTaskContainer container, ITask task, Set<TaskContainerDelta> delta) { + assert container.getChildren().contains(task); + + container.internalRemoveChild(task); + ((AbstractTask) task).removeParentContainer(container); + + delta.add(new TaskContainerDelta(task, container, TaskContainerDelta.Kind.REMOVED)); + } + + private void removeOrphan(AbstractTask task, Set<TaskContainerDelta> delta) { + AbstractTaskContainer orphans = getUnmatchedContainer(task.getRepositoryUrl()); + if (orphans != null) { + if (orphans.internalRemoveChild(task)) { + delta.add(new TaskContainerDelta(task, orphans, TaskContainerDelta.Kind.REMOVED)); + task.removeParentContainer(orphans); + } + } + } + + /** + * TODO separate category/query handle from name + * + * @deprecated + */ + @Deprecated + public void renameContainer(AbstractTaskContainer container, String newDescription) { + Assert.isLegal(!(container instanceof ITask)); + Assert.isLegal(!(container instanceof UnmatchedTaskContainer)); + try { + lock(); + if (container instanceof TaskCategory) { + ((TaskCategory) container).setSummary(newDescription); + } else if (container instanceof RepositoryQuery) { + ((RepositoryQuery) container).setSummary(newDescription); + } + delta.add(new TaskContainerDelta(container, TaskContainerDelta.Kind.CONTENT)); + } finally { + unlock(); + } + } + + /** + * Public for testing. + */ + public void reset() { + try { + lock(); + tasks = new ConcurrentHashMap<String, AbstractTask>(); + + repositoryOrphansMap = new ConcurrentHashMap<String, UnmatchedTaskContainer>(); + unsubmittedTasksMap = new ConcurrentHashMap<String, UnsubmittedTaskContainer>(); + categories = new ConcurrentHashMap<String, AbstractTaskCategory>(); + queries = new ConcurrentHashMap<String, RepositoryQuery>(); + + defaultCategory = new UncategorizedTaskContainer(); + + maxLocalTaskId = 0; + categories.put(defaultCategory.getHandleIdentifier(), defaultCategory); + } finally { + unlock(); + } + } + + public void run(ITaskListRunnable runnable) throws CoreException { + run(runnable, null); + } + + public void run(ITaskListRunnable runnable, IProgressMonitor monitor) throws CoreException { + monitor = Policy.monitorFor(monitor); + try { + lock(monitor); + + runnable.execute(monitor); + + } finally { + unlock(); + } + } + + private void lock() { + lock.acquire(); + if (lock.getDepth() == 1) { + delta = new HashSet<TaskContainerDelta>(); + } + } + + private void lock(IProgressMonitor monitor) throws CoreException { + while (!monitor.isCanceled()) { + try { + if (lock.acquire(3000)) { + if (lock.getDepth() == 1) { + delta = new HashSet<TaskContainerDelta>(); + } + return; + } + } catch (InterruptedException e) { + throw new OperationCanceledException(); + } + } + throw new OperationCanceledException(); + } + + private void unlock() { + HashSet<TaskContainerDelta> toFire = null; + if (lock.getDepth() == 1) { + toFire = new HashSet<TaskContainerDelta>(delta); + } + lock.release(); + if (toFire != null && toFire.size() > 0) { + fireDelta(toFire); + } + } + + public static ISchedulingRule getSchedulingRule() { + return ITasksCoreConstants.TASKLIST_SCHEDULING_RULE; + } + + public String getUniqueHandleIdentifier() { + try { + lock(); + while (nextHandle < Integer.MAX_VALUE) { + String handle = DEFAULT_HANDLE_PREFIX + nextHandle; + nextHandle++; + if (!categories.containsKey(handle) && !queries.containsKey(handle) + && !repositoryOrphansMap.containsKey(handle) && !tasks.containsKey(handle)) { + return handle; + } + } + throw new RuntimeException("No more unique handles for task list"); //$NON-NLS-1$ + } finally { + unlock(); + } + } + + public UnsubmittedTaskContainer getUnsubmittedContainer(String repositoryUrl) { + return unsubmittedTasksMap.get(repositoryUrl); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoriesExternalizer.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoriesExternalizer.java new file mode 100644 index 000000000..93d61fd13 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoriesExternalizer.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + * Jevgeni Holodkov - improvements + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +/** + * @author Rob Elves + * @author Jevgeni Holodkov + */ +public class TaskRepositoriesExternalizer { + + private final SaxRepositoriesWriter writer = new SaxRepositoriesWriter(); + + public static final String ELEMENT_TASK_REPOSITORIES = "TaskRepositories"; //$NON-NLS-1$ + + public static final String ELEMENT_TASK_REPOSITORY = "TaskRepository"; //$NON-NLS-1$ + + public static final String ATTRIBUTE_VERSION = "OutputVersion"; //$NON-NLS-1$ + + public void writeRepositoriesToXML(Collection<TaskRepository> repositories, File file) { + ZipOutputStream outputStream = null; + try { + if (!file.exists()) { + file.createNewFile(); + } + + outputStream = new ZipOutputStream(new FileOutputStream(file)); + writeRepositories(repositories, outputStream); + outputStream.close(); + + } catch (IOException e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Could not write: " //$NON-NLS-1$ + + file.getAbsolutePath(), e)); + } finally { + if (outputStream != null) { + try { + outputStream.close(); + } catch (IOException e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Could not close: " //$NON-NLS-1$ + + file.getAbsolutePath(), e)); + } + } + } + } + + /** + * @param repositories + * @param outputStream + * @throws IOException + */ + public void writeRepositories(Collection<TaskRepository> repositories, ZipOutputStream outputStream) + throws IOException { + ZipEntry zipEntry = new ZipEntry(TaskRepositoryManager.OLD_REPOSITORIES_FILE); + outputStream.putNextEntry(zipEntry); + outputStream.setMethod(ZipOutputStream.DEFLATED); + + // OutputStream stream = new FileOutputStream(file); + writer.setOutputStream(outputStream); + writer.writeRepositoriesToStream(repositories); + outputStream.flush(); + outputStream.closeEntry(); + } + + public Set<TaskRepository> readRepositoriesFromXML(File file) { + + if (!file.exists()) { + return null; + } + InputStream inputStream = null; + try { + inputStream = new ZipInputStream(new FileInputStream(file)); + + // search for REPOSITORIES entry + ZipEntry entry = ((ZipInputStream) inputStream).getNextEntry(); + while (entry != null) { + if (TaskRepositoryManager.OLD_REPOSITORIES_FILE.equals(entry.getName())) { + break; + } + entry = ((ZipInputStream) inputStream).getNextEntry(); + } + + if (entry == null) { + return null; + } + + SaxRepositoriesContentHandler contentHandler = new SaxRepositoriesContentHandler(); + XMLReader reader = XMLReaderFactory.createXMLReader(); + reader.setContentHandler(contentHandler); + reader.parse(new InputSource(inputStream)); + return contentHandler.getRepositories(); + } catch (Throwable e) { + file.renameTo(new File(file.getAbsolutePath() + "-save")); //$NON-NLS-1$ + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Error reading context file", e)); //$NON-NLS-1$ + return null; + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Error closing context file", e)); //$NON-NLS-1$ + } + } + } + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryAdapter.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryAdapter.java new file mode 100644 index 000000000..40a4d8956 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryAdapter.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import org.eclipse.mylyn.tasks.core.IRepositoryListener; +import org.eclipse.mylyn.tasks.core.TaskRepository; + +/** + * @author Rob Elves + * @since 3.0 + */ +public class TaskRepositoryAdapter implements IRepositoryListener { + + public void repositoryAdded(TaskRepository repository) { + // ignore + } + + public void repositoryRemoved(TaskRepository repository) { + // ignore + } + + public void repositorySettingsChanged(TaskRepository repository) { + // ignore + } + + public void repositoryUrlChanged(TaskRepository repository, String oldUrl) { + // ignore + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryLocation.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryLocation.java new file mode 100644 index 000000000..3f50f5dc5 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryLocation.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.net.Proxy; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.commons.net.AbstractWebLocation; +import org.eclipse.mylyn.commons.net.AuthenticationCredentials; +import org.eclipse.mylyn.commons.net.AuthenticationType; +import org.eclipse.mylyn.commons.net.WebUtil; +import org.eclipse.mylyn.tasks.core.RepositoryStatus; +import org.eclipse.mylyn.tasks.core.TaskRepository; + +/** + * @author Steffen Pingel + */ +public class TaskRepositoryLocation extends AbstractWebLocation { + + protected final TaskRepository taskRepository; + + public TaskRepositoryLocation(TaskRepository taskRepository) { + super(taskRepository.getRepositoryUrl()); + this.taskRepository = taskRepository; + } + + @Override + public Proxy getProxyForHost(String host, String proxyType) { + if (!taskRepository.isDefaultProxyEnabled()) { + String proxyHost = taskRepository.getProperty(TaskRepository.PROXY_HOSTNAME); + String proxyPort = taskRepository.getProperty(TaskRepository.PROXY_PORT); + if (proxyHost != null && proxyHost.length() > 0 && proxyPort != null) { + try { + int proxyPortNum = Integer.parseInt(proxyPort); + AuthenticationCredentials credentials = taskRepository.getCredentials(AuthenticationType.PROXY); + return WebUtil.createProxy(proxyHost, proxyPortNum, credentials); + } catch (NumberFormatException e) { + StatusHandler.log(new RepositoryStatus(taskRepository, IStatus.ERROR, + ITasksCoreConstants.ID_PLUGIN, 0, "Error occured while configuring proxy. Invalid port \"" //$NON-NLS-1$ + + proxyPort + "\" specified.", e)); //$NON-NLS-1$ + } + } + } + return WebUtil.getProxy(host, proxyType); + } + + @Override + public AuthenticationCredentials getCredentials(AuthenticationType type) { + return taskRepository.getCredentials(type); + } + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryManager.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryManager.java new file mode 100644 index 000000000..41cc0f436 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryManager.java @@ -0,0 +1,413 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + * Jevgeni Holodkov - improvements + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.ISafeRunnable; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.IRepositoryListener; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; + +/** + * Provides facilities for managing the life-cycle of and access to task repositories. + * + * @author Mik Kersten + * @author Rob Elves + * @author Jevgeni Holodkov + * @since 3.0 + */ +public class TaskRepositoryManager implements IRepositoryManager { + + public static final String OLD_REPOSITORIES_FILE = "repositories.xml"; //$NON-NLS-1$ + + public static final String DEFAULT_REPOSITORIES_FILE = "repositories.xml.zip"; //$NON-NLS-1$ + + public static final String PREF_REPOSITORIES = "org.eclipse.mylyn.tasklist.repositories."; //$NON-NLS-1$ + + private final Map<String, AbstractRepositoryConnector> repositoryConnectors = new HashMap<String, AbstractRepositoryConnector>(); + + private final Map<String, Set<TaskRepository>> repositoryMap = new HashMap<String, Set<TaskRepository>>(); + + private final Set<IRepositoryListener> listeners = new CopyOnWriteArraySet<IRepositoryListener>(); + + private final Set<TaskRepository> orphanedRepositories = new HashSet<TaskRepository>(); + + public static final String MESSAGE_NO_REPOSITORY = Messages.TaskRepositoryManager_No_repository_available; + + public static final String PREFIX_LOCAL = "local-"; //$NON-NLS-1$ + + private final PropertyChangeListener PROPERTY_CHANGE_LISTENER = new PropertyChangeListener() { + + public void propertyChange(PropertyChangeEvent evt) { + TaskRepositoryManager.this.notifyRepositorySettingsChanged((TaskRepository) evt.getSource()); + } + }; + + private final TaskRepositoriesExternalizer externalizer = new TaskRepositoriesExternalizer(); + + public TaskRepositoryManager() { + } + + public Collection<AbstractRepositoryConnector> getRepositoryConnectors() { + return Collections.unmodifiableCollection(repositoryConnectors.values()); + } + + public AbstractRepositoryConnector getRepositoryConnector(String connectorKind) { + return repositoryConnectors.get(connectorKind); + } + + /** + * primarily for testing + */ + public AbstractRepositoryConnector removeRepositoryConnector(String connectorKind) { + return repositoryConnectors.remove(connectorKind); + } + + public void addRepositoryConnector(AbstractRepositoryConnector repositoryConnector) { + if (!repositoryConnectors.values().contains(repositoryConnector)) { + repositoryConnectors.put(repositoryConnector.getConnectorKind(), repositoryConnector); + } + } + + public boolean hasUserManagedRepositoryConnectors() { + for (AbstractRepositoryConnector connector : repositoryConnectors.values()) { + if (connector.isUserManaged()) { + return true; + } + } + return false; + } + + public void addRepository(final TaskRepository repository) { + Set<TaskRepository> repositories; + if (!repositoryMap.containsKey(repository.getConnectorKind())) { + repositories = new HashSet<TaskRepository>(); + repositoryMap.put(repository.getConnectorKind(), repositories); + } else { + repositories = repositoryMap.get(repository.getConnectorKind()); + } + repositories.add(repository); + repository.addChangeListener(PROPERTY_CHANGE_LISTENER); + for (final IRepositoryListener listener : listeners) { + SafeRunner.run(new ISafeRunnable() { + public void handleException(Throwable e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Listener failed: " //$NON-NLS-1$ + + listener.getClass(), e)); + } + + public void run() throws Exception { + listener.repositoryAdded(repository); + } + }); + } + } + + public void removeRepository(final TaskRepository repository, String repositoryFilePath) { + Set<TaskRepository> repositories = repositoryMap.get(repository.getConnectorKind()); + if (repositories != null) { + repository.flushAuthenticationCredentials(); + repositories.remove(repository); + } + repository.removeChangeListener(PROPERTY_CHANGE_LISTENER); + saveRepositories(repositoryFilePath); + for (final IRepositoryListener listener : listeners) { + SafeRunner.run(new ISafeRunnable() { + public void handleException(Throwable e) { + StatusHandler.log(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Listener failed: " //$NON-NLS-1$ + + listener.getClass(), e)); + } + + public void run() throws Exception { + listener.repositoryRemoved(repository); + } + }); + } + } + + public void addListener(IRepositoryListener listener) { + listeners.add(listener); + } + + public void removeListener(IRepositoryListener listener) { + listeners.remove(listener); + } + + /* Public for testing. */ + public static String stripSlashes(String url) { + Assert.isNotNull(url); + StringBuilder sb = new StringBuilder(url.trim()); + while (sb.length() > 0 && sb.charAt(sb.length() - 1) == '/') { + sb.deleteCharAt(sb.length() - 1); + } + return sb.toString(); + } + + public TaskRepository getRepository(String kind, String urlString) { + Assert.isNotNull(kind); + Assert.isNotNull(urlString); + urlString = stripSlashes(urlString); + if (repositoryMap.containsKey(kind)) { + for (TaskRepository repository : repositoryMap.get(kind)) { + if (stripSlashes(repository.getRepositoryUrl()).equals(urlString)) { + return repository; + } + } + } + return null; + } + + /** + * @return first repository that matches the given url + */ + public TaskRepository getRepository(String urlString) { + Assert.isNotNull(urlString); + urlString = stripSlashes(urlString); + for (String kind : repositoryMap.keySet()) { + for (TaskRepository repository : repositoryMap.get(kind)) { + if (stripSlashes(repository.getRepositoryUrl()).equals(urlString)) { + return repository; + } + } + } + return null; + } + + /** + * @return the first connector to accept the URL + */ + public AbstractRepositoryConnector getConnectorForRepositoryTaskUrl(String url) { + Assert.isNotNull(url); + for (AbstractRepositoryConnector connector : getRepositoryConnectors()) { + if (connector.getRepositoryUrlFromTaskUrl(url) != null) { + for (TaskRepository repository : getRepositories(connector.getConnectorKind())) { + if (url.startsWith(repository.getRepositoryUrl())) { + return connector; + } + } + } + } + return null; + } + + public Set<TaskRepository> getRepositories(String kind) { + Assert.isNotNull(kind); + if (repositoryMap.containsKey(kind)) { + return repositoryMap.get(kind); + } else { + return Collections.emptySet(); + } + } + + public List<TaskRepository> getAllRepositories() { + List<TaskRepository> repositories = new ArrayList<TaskRepository>(); + for (AbstractRepositoryConnector repositoryConnector : repositoryConnectors.values()) { + if (repositoryMap.containsKey(repositoryConnector.getConnectorKind())) { + repositories.addAll(repositoryMap.get(repositoryConnector.getConnectorKind())); + } + } + return repositories; + } + + /** + * TODO: implement default support, this just returns first found + */ + public TaskRepository getDefaultRepository(String kind) { + // HACK: returns first repository found + if (repositoryMap.containsKey(kind)) { + for (TaskRepository repository : repositoryMap.get(kind)) { + return repository; + } + } else { + Collection<Set<TaskRepository>> values = repositoryMap.values(); + if (!values.isEmpty()) { + Set<TaskRepository> repoistorySet = values.iterator().next(); + return repoistorySet.iterator().next(); + } + } + return null; + } + + Map<String, Set<TaskRepository>> readRepositories(String repositoriesFilePath) { + + repositoryMap.clear(); + orphanedRepositories.clear(); + + loadRepositories(repositoriesFilePath); + +// for (IRepositoryListener listener : listeners) { +// try { +// listener.repositoriesRead(); +// } catch (Throwable t) { +// StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, +// "Repository listener failed", t)); +// } +// } + return repositoryMap; + } + + private void loadRepositories(String repositoriesFilePath) { + boolean migration = false; + // String dataDirectory = + // TasksUiPlugin.getDefault().getDataDirectory(); + File repositoriesFile = new File(repositoriesFilePath); + + // Will only load repositories for which a connector exists + for (AbstractRepositoryConnector repositoryConnector : repositoryConnectors.values()) { + repositoryMap.put(repositoryConnector.getConnectorKind(), new HashSet<TaskRepository>()); + } + if (repositoriesFile.exists()) { + Set<TaskRepository> repositories = externalizer.readRepositoriesFromXML(repositoriesFile); + if (repositories != null && repositories.size() > 0) { + for (TaskRepository repository : repositories) { + if (removeHttpAuthMigration(repository)) { + migration = true; + } + if (repositoryMap.containsKey(repository.getConnectorKind())) { + repositoryMap.get(repository.getConnectorKind()).add(repository); + repository.addChangeListener(PROPERTY_CHANGE_LISTENER); + } else { + orphanedRepositories.add(repository); + } + } + } + if (migration) { + saveRepositories(repositoriesFilePath); + } + } + } + + @SuppressWarnings("deprecation") + private boolean removeHttpAuthMigration(TaskRepository repository) { + String httpusername = repository.getProperty(TaskRepository.AUTH_HTTP_USERNAME); + String httppassword = repository.getProperty(TaskRepository.AUTH_HTTP_PASSWORD); + if (httpusername != null && httppassword != null) { + repository.removeProperty(TaskRepository.AUTH_HTTP_USERNAME); + repository.removeProperty(TaskRepository.AUTH_HTTP_PASSWORD); + if (httpusername.length() > 0 && httppassword.length() > 0) { + repository.setHttpAuthenticationCredentials(httpusername, httppassword); + } + return true; + } + return false; + } + + protected synchronized boolean saveRepositories(String destinationPath) { +// if (!Platform.isRunning()) {// || TasksUiPlugin.getDefault() == null) { +// return false; +// } + Set<TaskRepository> repositoriesToWrite = new HashSet<TaskRepository>(getAllRepositories()); + // if for some reason a repository is added/changed to equal one in the + // orphaned set the orphan is discarded + for (TaskRepository repository : orphanedRepositories) { + if (!repositoriesToWrite.contains(repository)) { + repositoriesToWrite.add(repository); + } + } + + try { + File repositoriesFile = new File(destinationPath); + externalizer.writeRepositoriesToXML(repositoriesToWrite, repositoriesFile); + } catch (Throwable t) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Could not save repositories", t)); //$NON-NLS-1$ + return false; + } + return true; + } + + /** + * For testing. + */ + public void clearRepositories(String repositoriesFilePath) { + repositoryMap.clear(); + orphanedRepositories.clear(); + saveRepositories(repositoriesFilePath); + } + + public void notifyRepositorySettingsChanged(final TaskRepository repository) { + for (final IRepositoryListener listener : listeners) { + SafeRunner.run(new ISafeRunnable() { + public void handleException(Throwable e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Listener failed: " //$NON-NLS-1$ + + listener.getClass(), e)); + } + + public void run() throws Exception { + listener.repositorySettingsChanged(repository); + } + }); + } + } + + public void insertRepositories(Set<TaskRepository> repositories, String repositoryFilePath) { + for (TaskRepository repository : repositories) { + if (getRepository(repository.getConnectorKind(), repository.getRepositoryUrl()) == null) { + addRepository(repository); + } + } + } + + public boolean isOwnedByUser(ITask task) { + if (task instanceof LocalTask) { + return true; + } + + ITask repositoryTask = task; + TaskRepository repository = getRepository(repositoryTask.getConnectorKind(), repositoryTask.getRepositoryUrl()); + if (repository != null && repositoryTask.getOwner() != null) { + return repositoryTask.getOwner().equals(repository.getUserName()); + } + + return false; + } + + /** + * @param repository + * with new url + * @param oldUrl + * previous url for this repository + */ + public void notifyRepositoryUrlChanged(final TaskRepository repository, final String oldUrl) { + for (final IRepositoryListener listener : listeners) { + SafeRunner.run(new ISafeRunnable() { + public void handleException(Throwable e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Listener failed: " //$NON-NLS-1$ + + listener.getClass(), e)); + } + + public void run() throws Exception { + listener.repositoryUrlChanged(repository, oldUrl); + } + }); + } + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskTask.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskTask.java new file mode 100644 index 000000000..000f75426 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskTask.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +/** + * @author Steffen Pingel + */ +public class TaskTask extends AbstractTask { + + private final String connectorKind; + + public TaskTask(String connectorKind, String repositoryUrl, String taskId) { + super(repositoryUrl, taskId, ""); //$NON-NLS-1$ + this.connectorKind = connectorKind; + this.taskKey = taskId; + } + + @Override + public String getConnectorKind() { + return connectorKind; + } + + @Override + public String getTaskKey() { + return taskKey; + } + + @Override + public boolean isLocal() { + return false; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TransferList.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TransferList.java new file mode 100644 index 000000000..a144aea4b --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TransferList.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.mylyn.tasks.core.ITask; + +/** + * Used to externalize queries. + * + * @author Steffen + */ +public class TransferList implements ITransferList { + + private final Set<RepositoryQuery> queries; + + private final List<AbstractTask> tasks; + + private final Set<AbstractTaskCategory> categories; + + public TransferList() { + this.queries = new HashSet<RepositoryQuery>(); + this.tasks = new ArrayList<AbstractTask>(); + this.categories = new HashSet<AbstractTaskCategory>(); + } + + public TransferList(Set<AbstractTaskCategory> categories, Set<RepositoryQuery> queries, List<AbstractTask> tasks) { + this.tasks = new ArrayList<AbstractTask>(tasks); + this.queries = new HashSet<RepositoryQuery>(queries); + this.categories = new HashSet<AbstractTaskCategory>(categories); + } + + public void addCategory(TaskCategory category) { + categories.add(category); + } + + public void addQuery(RepositoryQuery query) { + queries.add(query); + } + + public void addTask(ITask task) { + tasks.add((AbstractTask) task); + } + + public boolean addTask(ITask task, AbstractTaskContainer parentContainer) { + tasks.add((AbstractTask) task); + return true; + } + + public Collection<AbstractTask> getAllTasks() { + return tasks; + } + + public Set<AbstractTaskCategory> getCategories() { + return categories; + } + + public AbstractTaskCategory getContainerForHandle(String handle) { + Assert.isNotNull(handle); + for (AbstractTaskCategory category : categories) { + if (category.getHandleIdentifier().equals(handle)) { + return category; + } + } + return null; + } + + public Set<RepositoryQuery> getQueries() { + return queries; + } + + public AbstractTask getTask(String handleIdentifier) { + Assert.isNotNull(handleIdentifier); + for (AbstractTask task : tasks) { + if (task.getHandleIdentifier().equals(handleIdentifier)) { + return task; + } + } + return null; + } + + public ITask getTask(String repositoryUrl, String taskId) { + Assert.isNotNull(repositoryUrl); + Assert.isNotNull(taskId); + for (AbstractTask task : tasks) { + if (task.getRepositoryUrl().equals(repositoryUrl) && task.getTaskId().equals(taskId)) { + return task; + } + } + return null; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UncategorizedTaskContainer.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UncategorizedTaskContainer.java new file mode 100644 index 000000000..749512458 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UncategorizedTaskContainer.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import org.eclipse.mylyn.tasks.core.ITask.PriorityLevel; + +/** + * Category created for the user to hold uncategorized tasks. + * + * @author Rob Elves + */ +public class UncategorizedTaskContainer extends AbstractTaskCategory { + + public static final String LABEL = Messages.UncategorizedTaskContainer_Uncategorized; + + public static final String HANDLE = LABEL; + + public UncategorizedTaskContainer() { + super(HANDLE); + } + + @Override + public String getPriority() { + return PriorityLevel.P1.toString(); + } + + @Override + public String getHandleIdentifier() { + return HANDLE; + } + + @Override + public String getSummary() { + return LABEL; + } + + @Override + public boolean isUserManaged() { + return false; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UnmatchedTaskContainer.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UnmatchedTaskContainer.java new file mode 100644 index 000000000..32c7e5fab --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UnmatchedTaskContainer.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +/** + * Holds orphaned or uncategorized tasks for a given repository + * + * @author Rob Elves + * @author Mik Kersten + */ +public class UnmatchedTaskContainer extends AutomaticRepositoryTaskContainer { + + private static final String HANDLE = "orphans"; //$NON-NLS-1$ + + public UnmatchedTaskContainer(String connectorKind, String repositoryUrl) { + super(HANDLE, connectorKind, repositoryUrl); + } + + @Override + public String getSummary() { + return Messages.UnmatchedTaskContainer_Unmatched; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UnsubmittedTaskContainer.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UnsubmittedTaskContainer.java new file mode 100644 index 000000000..624d7770f --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UnsubmittedTaskContainer.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +/** + * @author Rob Elves + * @author Mik Kersten + */ +public class UnsubmittedTaskContainer extends AutomaticRepositoryTaskContainer { + + private static final String HANDLE = "unsubmitted"; //$NON-NLS-1$ + + public UnsubmittedTaskContainer(String connectorKind, String repositoryUrl) { + super(HANDLE, connectorKind, repositoryUrl); + } + + @Override + public String getSummary() { + return Messages.UnsubmittedTaskContainer_Unsubmitted; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/WeekDateRange.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/WeekDateRange.java new file mode 100644 index 000000000..7c3b398c6 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/WeekDateRange.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +/** + * @author Rob Elves + */ +public class WeekDateRange extends DateRange { + + private static final String DESCRIPTION_WEEK_AFTER_NEXT = Messages.WeekDateRange_Two_Weeks; + + private static final String DESCRIPTION_PREVIOUS_WEEK = Messages.WeekDateRange_Previous_Week; + + private static final String DESCRIPTION_THIS_WEEK = Messages.WeekDateRange_This_Week; + + private static final String DESCRIPTION_NEXT_WEEK = Messages.WeekDateRange_Next_Week; + + private final List<DayDateRange> days = new ArrayList<DayDateRange>(); + + public WeekDateRange(Calendar startDate, Calendar endDate) { + super(startDate, endDate); + } + + public List<DateRange> getRemainingDays() { + List<DateRange> remainingDays = new ArrayList<DateRange>(); + for (DateRange dayDateRange : getDaysOfWeek()) { + if (!dayDateRange.isPast()) { + remainingDays.add(dayDateRange); + } + } + return remainingDays; + } + + public List<DayDateRange> getDaysOfWeek() { + if (days.isEmpty()) { + for (int x = TaskActivityUtil.getStartDay(); x < (TaskActivityUtil.getStartDay() + 7); x++) { + Calendar dayStart = TaskActivityUtil.getCalendar(); + dayStart.setTime(getStartDate().getTime()); + TaskActivityUtil.snapStartOfDay(dayStart); + + Calendar dayEnd = TaskActivityUtil.getCalendar(); + dayEnd.setTime(getStartDate().getTime()); + TaskActivityUtil.snapEndOfDay(dayEnd); + + if (x > 7) { + dayStart.set(Calendar.DAY_OF_WEEK, x % 7); + dayEnd.set(Calendar.DAY_OF_WEEK, x % 7); + } else { + dayStart.set(Calendar.DAY_OF_WEEK, x); + dayEnd.set(Calendar.DAY_OF_WEEK, x); + } + + days.add(new DayDateRange(dayStart, dayEnd)); + } + } + return days; + } + + /** + * @return today's DayDateRange, null if does not exist (now > endDate) + */ + public DayDateRange getToday() { + DayDateRange today = null; + Calendar now = TaskActivityUtil.getCalendar(); + for (DayDateRange range : getDaysOfWeek()) { + if (range.includes(now)) { + today = range; + break; + } + } + if (today == null) { + Calendar todayStart = TaskActivityUtil.getCalendar(); + TaskActivityUtil.snapStartOfDay(todayStart); + Calendar todayEnd = TaskActivityUtil.getCalendar(); + TaskActivityUtil.snapEndOfDay(todayEnd); + today = new DayDateRange(todayStart, todayEnd); + } + return today; + } + + public boolean isCurrentWeekDay(DateRange range) { + if (range == null) { + return false; + } + return getDaysOfWeek().contains(range); + } + + private boolean isNextWeek() { + return TaskActivityUtil.getNextWeek().compareTo(this) == 0; + } + + public boolean isThisWeek() { + //if (isWeek()) { + return this.includes(Calendar.getInstance()); + //} + //return false; + } + + private boolean isPreviousWeek() { + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.WEEK_OF_YEAR, -1); + return this.includes(cal); + } + + private boolean isWeekAfterNext() { + return TaskActivityUtil.getNextWeek().next().compareTo(this) == 0; + } + + public WeekDateRange next() { + return create(Calendar.WEEK_OF_YEAR, 1); + } + + public WeekDateRange previous() { + return create(Calendar.WEEK_OF_YEAR, -1); + } + + protected WeekDateRange create(int field, int multiplier) { + Calendar previousStart = (Calendar) getStartDate().clone(); + Calendar previousEnd = (Calendar) getEndDate().clone(); + previousStart.add(field, 1 * multiplier); + previousEnd.add(field, 1 * multiplier); + return new WeekDateRange(previousStart, previousEnd); + } + + @Override + public String toString(boolean useDayOfWeekForNextWeek) { + if (isWeekAfterNext()) { + return DESCRIPTION_WEEK_AFTER_NEXT; + } else if (isThisWeek()) { + return DESCRIPTION_THIS_WEEK; + } else if (isNextWeek()) { + return DESCRIPTION_NEXT_WEEK; + } else if (isPreviousWeek()) { + return DESCRIPTION_PREVIOUS_WEEK; + } + return super.toString(useDayOfWeekForNextWeek); + } + + public DateRange getDayOfWeek(int dayNum) { + if (dayNum > 0 && dayNum <= 7) { + for (DateRange day : getDaysOfWeek()) { + if (day.getStartDate().get(Calendar.DAY_OF_WEEK) == dayNum) { + return day; + } + } + } + throw new IllegalArgumentException("Valid day values are 1 - 7"); //$NON-NLS-1$ + } + + public static boolean isWeekRange(Calendar calStart, Calendar calEnd) { + // bug 248683 + long diff = (calEnd.getTimeInMillis() - calStart.getTimeInMillis()) - (DAY * 7 - 1); + return Math.abs(diff) <= 60 * 60 * 1000; + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ElementHandler.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ElementHandler.java new file mode 100644 index 000000000..5d251addc --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ElementHandler.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.data; + +import java.util.HashMap; +import java.util.Map; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +class ElementHandler extends DefaultHandler { + + protected final StringBuilder currentElementText; + + private ElementHandler currentHandler; + + private final String elementName; + + private final Map<String, ElementHandler> handlers; + + private final ElementHandler parent; + + public ElementHandler(ElementHandler parent, String elementName) { + this.parent = parent; + this.elementName = elementName; + this.handlers = new HashMap<String, ElementHandler>(); + this.currentElementText = new StringBuilder(); + } + + public void addElementHandler(ElementHandler handler) { + handlers.put(handler.getElementName(), handler); + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (currentHandler != null) { + currentHandler.characters(ch, start, length); + } else { + currentElementText.append(ch, start, length); + } + } + + protected void done(ElementHandler elementHandler) { + currentHandler = null; + } + + protected void end(String uri, String localName, String name) { + } + + @Override + public void endElement(String uri, String localName, String name) throws SAXException { + if (currentHandler != null) { + currentHandler.endElement(uri, localName, name); + } else if (elementName.equals(localName)) { + end(uri, localName, name); + if (parent != null) { + parent.done(this); + } + } + } + + protected void clearCurrentElementText() { + currentElementText.setLength(0); + } + + protected String getCurrentElementText() { + return currentElementText.toString(); + } + + public String getElementName() { + return elementName; + } + + protected String getOptionalValue(Attributes attributes, String name) throws SAXException { + String value = attributes.getValue(name); + if (value == null) { + return ""; //$NON-NLS-1$ + } + return value; + } + + public ElementHandler getParent() { + return parent; + } + + protected String getValue(Attributes attributes, String name) throws SAXException { + String value = attributes.getValue(name); + if (value == null) { + throw new SAXException("Missing required attribute \"" + name + "\""); //$NON-NLS-1$ //$NON-NLS-2$ + } + return value; + } + + public void removeElementHandler(ElementHandler handler) { + handlers.remove(handler.getElementName()); + } + + protected void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + } + + @Override + public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { + if (currentHandler == null) { + ElementHandler handler = handlers.get(name); + if (handler != null) { + currentHandler = handler; + currentHandler.start(uri, localName, name, attributes); + } + } else if (currentHandler != null) { + currentHandler.startElement(uri, localName, name, attributes); + } + } +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/FileTaskAttachmentSource.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/FileTaskAttachmentSource.java new file mode 100644 index 000000000..c14c22467 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/FileTaskAttachmentSource.java @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2004, 2009 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + * David Green - fix for 267960 + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.data; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.content.IContentType; +import org.eclipse.core.runtime.content.IContentTypeManager; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskAttachmentSource; + +/** + * @author Steffen Pingel + * @author David Green + */ +public class FileTaskAttachmentSource extends AbstractTaskAttachmentSource { + /** + * mime type for text/plain + */ + private static final String TEXT_PLAIN = "text/plain"; //$NON-NLS-1$ + + /** + * mime type for application/xml + */ + private static final String APPLICATION_XML = "application/xml"; //$NON-NLS-1$ + + public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; //$NON-NLS-1$ + + private static Map<String, String> extensions2Types; + + static { + // see http://www.iana.org/assignments/media-types/ + extensions2Types = new HashMap<String, String>(); + extensions2Types.put("txt", TEXT_PLAIN); //$NON-NLS-1$ + extensions2Types.put("html", "text/html"); //$NON-NLS-1$ //$NON-NLS-2$ + extensions2Types.put("htm", "text/html"); //$NON-NLS-1$ //$NON-NLS-2$ + extensions2Types.put("xhtml", "application/xhtml+xml"); //$NON-NLS-1$//$NON-NLS-2$ + extensions2Types.put("jpg", "image/jpeg"); //$NON-NLS-1$ //$NON-NLS-2$ + extensions2Types.put("jpeg", "image/jpeg"); //$NON-NLS-1$ //$NON-NLS-2$ + extensions2Types.put("gif", "image/gif"); //$NON-NLS-1$ //$NON-NLS-2$ + extensions2Types.put("png", "image/png"); //$NON-NLS-1$ //$NON-NLS-2$ + extensions2Types.put("xml", APPLICATION_XML); //$NON-NLS-1$ + extensions2Types.put("zip", APPLICATION_OCTET_STREAM); //$NON-NLS-1$ + extensions2Types.put("tar", APPLICATION_OCTET_STREAM); //$NON-NLS-1$ + extensions2Types.put("gz", APPLICATION_OCTET_STREAM); //$NON-NLS-1$ + } + + public static String getContentTypeFromFilename(String fileName) { + int index = fileName.lastIndexOf("."); //$NON-NLS-1$ + if (index > 0 && index < fileName.length()) { + String ext = fileName.substring(index + 1); + String type = extensions2Types.get(ext.toLowerCase(Locale.ENGLISH)); + if (type != null) { + return type; + } + // bug 267960 attempt to detect the mime type from the content type + IContentTypeManager contentTypeManager = Platform.getContentTypeManager(); + // platform may not be available when running standalone + if (contentTypeManager != null) { + IContentType contentType = contentTypeManager.findContentTypeFor(fileName); + while (contentType != null) { + if (IContentTypeManager.CT_TEXT.equals(contentType.getId())) { + return TEXT_PLAIN; + } else if ("org.eclipse.core.runtime.xml".equals(contentType.getId())) { //$NON-NLS-1$ + return APPLICATION_XML; + } + contentType = contentType.getBaseType(); + } + } + } + + // fall back to a safe mime type + return APPLICATION_OCTET_STREAM; + } + + private String contentType; + + private String description; + + private final File file; + + private String name; + + public FileTaskAttachmentSource(File file) { + this.file = file; + this.name = file.getName(); + this.contentType = getContentTypeFromFilename(name); + } + + @Override + public InputStream createInputStream(IProgressMonitor monitor) throws CoreException { + try { + return new FileInputStream(file); + } catch (FileNotFoundException e) { + throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, e.getMessage(), e)); + } + } + + @Override + public String getContentType() { + return contentType; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public long getLength() { + return file.length(); + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isLocal() { + return true; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setName(String name) { + this.name = name; + } + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ITaskDataConstants.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ITaskDataConstants.java new file mode 100644 index 000000000..114ebf40c --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ITaskDataConstants.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.data; + +public interface ITaskDataConstants { + + static final String ATTRIBUTE_TASK_KIND = "taskKind"; //$NON-NLS-1$ + + static final String ATTRIBUTE_IS_PATCH = "isPatch"; //$NON-NLS-1$ + + static final String ATTRIBUTE_IS_OBSOLETE = "isObsolete"; //$NON-NLS-1$ + + static final String ATTRIBUTE_CREATOR = "creator"; //$NON-NLS-1$ + + static final String ATTRIBUTE_NUMBER = "number"; //$NON-NLS-1$ + + static final String ATTRIBUTE_HAS_ATTACHMENT = "hasAttachment"; //$NON-NLS-1$ + + static final String ATTRIBUTE_ATTACHMENT_ID = "attachmentId"; //$NON-NLS-1$ + + static final String ATTRIBUTE_KNOB_NAME = "knob_name"; //$NON-NLS-1$ + + static final String ATTRIBUTE_OPERATION_NAME = "operationName"; //$NON-NLS-1$ + + static final String ATTRIBUTE_OPTION_NAME = "optionName"; //$NON-NLS-1$ + + static final String ATTRIBUTE_OPTION_SELECTION = "optionSelection"; //$NON-NLS-1$ + + static final String ATTRIBUTE_IS_CHECKED = "isChecked"; //$NON-NLS-1$ + + static final String ATTRIBUTE_INPUT_NAME = "inputName"; //$NON-NLS-1$ + + static final String ATTRIBUTE_INPUT_VALUE = "inputValue"; //$NON-NLS-1$ + + static final String ATTRIBUTE_READONLY = "readonly"; //$NON-NLS-1$ + + static final String ATTRIBUTE_HIDDEN = "hidden"; //$NON-NLS-1$ + + static final String ATTRIBUTE_PARAMETER = "parameter"; //$NON-NLS-1$ + + static final String ATTRIBUTE_VALUE = "value"; //$NON-NLS-1$ + + static final String ELEMENT_META = "meta"; //$NON-NLS-1$ + + static final String ELEMENT_OPTION = "option"; //$NON-NLS-1$ + + static final String ELEMENT_VALUE = "value"; //$NON-NLS-1$ + + static final String ELEMENT_ATTRIBUTE = "Attribute"; //$NON-NLS-1$ + + static final String ELEMENT_NAME = "name"; //$NON-NLS-1$ + + static final String ELEMENT_VALUES = "values"; //$NON-NLS-1$ + + static final String ELEMENT_OPTIONS = "options"; //$NON-NLS-1$ + + static final String ELEMENT_META_DATA = "MetaData"; //$NON-NLS-1$ + + static final String ELEMENT_OPERATION = "Operation"; //$NON-NLS-1$ + + static final String ELEMENT_COMMENT = "Comment"; //$NON-NLS-1$ + + static final String ELEMENT_ATTACHMENT = "Attachment"; //$NON-NLS-1$ + + static final String ATTRIBUTE_REPOSITORY_KIND = "repositoryKind"; //$NON-NLS-1$ + + static final String ATTRIBUTE_CONNECTOR_KIND = "connectorKind"; //$NON-NLS-1$ + + static final String ATTRIBUTE_REPOSITORY_URL = "repositoryUrl"; //$NON-NLS-1$ + + static final String ATTRIBUTE_KEY = "key"; //$NON-NLS-1$ + + static final String ATTRIBUTE_ID = "id"; //$NON-NLS-1$ + + static final String ATTRIBUTE_NAME = "name"; //$NON-NLS-1$ + + static final String ELEMENT_EDITS_DATA = "EditsData"; //$NON-NLS-1$ + + static final String ELEMENT_OLD_DATA = "OldData"; //$NON-NLS-1$ + + static final String ELEMENT_NEW_DATA = "NewData"; //$NON-NLS-1$ + + static final String ATTRIBUTE_VERSION = "version"; //$NON-NLS-1$ + + static final String ELEMENT_TASK_STATE = "TaskState"; //$NON-NLS-1$ + + static final String ELEMENT_KEY = "key"; //$NON-NLS-1$ + + static final String ATTRIBUTE_TASK_ID = "taskId"; //$NON-NLS-1$ + + static final String ELEMENT_ATTRIBUTES = "Attributes"; //$NON-NLS-1$ + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ITaskDataManagerListener.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ITaskDataManagerListener.java new file mode 100644 index 000000000..b16649591 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ITaskDataManagerListener.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.data; + +/** + * @author Steffen Pingel + */ +public interface ITaskDataManagerListener { + + public abstract void taskDataUpdated(TaskDataManagerEvent event); + + public abstract void editsDiscarded(TaskDataManagerEvent event); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataExternalizer.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataExternalizer.java new file mode 100644 index 000000000..4336296cd --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataExternalizer.java @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.data; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamResult; + +import org.eclipse.core.runtime.ISafeRunnable; +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler; +import org.eclipse.mylyn.tasks.core.data.ITaskDataWorkingCopy; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +/** + * @author Steffen Pingel + */ +public class TaskDataExternalizer { + + private final IRepositoryManager taskRepositoryManager; + + public TaskDataExternalizer(IRepositoryManager taskRepositoryManager) { + this.taskRepositoryManager = taskRepositoryManager; + } + + private void migrate(final TaskDataState taskDataState) throws IOException { + // for testing + if (taskRepositoryManager == null) { + return; + } + + String connectorKind = taskDataState.getConnectorKind(); + AbstractRepositoryConnector connector = taskRepositoryManager.getRepositoryConnector(connectorKind); + if (connector == null) { + throw new IOException("No repository connector for kind \"" + connectorKind + "\" found"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + String repositoryUrl = taskDataState.getRepositoryUrl(); + final TaskRepository taskRepository = taskRepositoryManager.getRepository(connectorKind, repositoryUrl); + if (taskRepository == null) { + throw new IOException("Repository \"" + repositoryUrl + "\" not found for kind \"" + connectorKind + "\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + final AbstractTaskDataHandler taskDataHandler = connector.getTaskDataHandler(); + if (taskDataHandler != null) { + migrate(taskDataState.getLastReadData(), taskRepository, taskDataHandler); + migrate(taskDataState.getRepositoryData(), taskRepository, taskDataHandler); + migrate(taskDataState.getEditsData(), taskRepository, taskDataHandler); + } + } + + private void migrate(final TaskData taskData, final TaskRepository taskRepository, + final AbstractTaskDataHandler taskDataHandler) { + if (taskData != null) { + SafeRunner.run(new ISafeRunnable() { + + public void handleException(Throwable exception) { + // ignore + } + + public void run() throws Exception { + taskDataHandler.migrateTaskData(taskRepository, taskData); + } + + }); + } + } + + public TaskDataState readState(InputStream in) throws IOException { + try { + XMLReader parser = XMLReaderFactory.createXMLReader(); + TaskDataStateReader handler = new TaskDataStateReader(taskRepositoryManager); + parser.setContentHandler(handler); + parser.parse(new InputSource(in)); + TaskDataState taskDataState = handler.getTaskDataState(); + if (taskDataState != null) { + migrate(taskDataState); + } + return taskDataState; + } catch (SAXException e) { + //e.printStackTrace(); + throw new IOException("Error parsing task data: " + e.getMessage()); //$NON-NLS-1$ + } + } + + public void writeState(OutputStream out, ITaskDataWorkingCopy state) throws IOException { + try { + SAXTransformerFactory transformerFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); + TransformerHandler handler = transformerFactory.newTransformerHandler(); + Transformer serializer = handler.getTransformer(); + serializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$ + serializer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ + handler.setResult(new StreamResult(out)); + TaskDataStateWriter writer = new TaskDataStateWriter(handler); + writer.write(state); + } catch (TransformerException e) { + throw new IOException("Error writing task data" + e.getMessageAndLocation()); //$NON-NLS-1$ + } catch (SAXException e) { + throw new IOException("Error writing task data" + e.getMessage()); //$NON-NLS-1$ + } + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataManager.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataManager.java new file mode 100644 index 000000000..c54a8debc --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataManager.java @@ -0,0 +1,548 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.data; + +import java.io.File; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Date; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.ISafeRunnable; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.internal.tasks.core.AbstractTask; +import org.eclipse.mylyn.internal.tasks.core.ITaskListRunnable; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.internal.tasks.core.TaskActivityManager; +import org.eclipse.mylyn.internal.tasks.core.TaskList; +import org.eclipse.mylyn.internal.tasks.core.TaskTask; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.ITask.SynchronizationState; +import org.eclipse.mylyn.tasks.core.data.ITaskDataManager; +import org.eclipse.mylyn.tasks.core.data.ITaskDataWorkingCopy; +import org.eclipse.mylyn.tasks.core.data.TaskData; + +/** + * Encapsulates synchronization policy. + * + * @author Mik Kersten + * @author Rob Elves + * @author Steffen Pingel + */ +public class TaskDataManager implements ITaskDataManager { + + private static final String ENCODING_UTF_8 = "UTF-8"; //$NON-NLS-1$ + + private static final String EXTENSION = ".zip"; //$NON-NLS-1$ + + private static final String FOLDER_TASKS = "tasks"; //$NON-NLS-1$ + + private static final String FOLDER_DATA = "offline"; //$NON-NLS-1$ + + private static final String FOLDER_TASKS_1_0 = "offline"; //$NON-NLS-1$ + + private String dataPath; + + private final IRepositoryManager repositoryManager; + + private final TaskDataStore taskDataStore; + + private final TaskList taskList; + + private final TaskActivityManager taskActivityManager; + + private final List<ITaskDataManagerListener> listeners = new CopyOnWriteArrayList<ITaskDataManagerListener>(); + + public TaskDataManager(TaskDataStore taskDataStore, IRepositoryManager repositoryManager, TaskList taskList, + TaskActivityManager taskActivityManager) { + this.taskDataStore = taskDataStore; + this.repositoryManager = repositoryManager; + this.taskList = taskList; + this.taskActivityManager = taskActivityManager; + } + + public void addListener(ITaskDataManagerListener listener) { + listeners.add(listener); + } + + public void removeListener(ITaskDataManagerListener listener) { + listeners.remove(listener); + } + + public ITaskDataWorkingCopy createWorkingCopy(final ITask task, final TaskData taskData) { + Assert.isNotNull(task); + final TaskDataState state = new TaskDataState(taskData.getConnectorKind(), taskData.getRepositoryUrl(), + taskData.getTaskId()); + state.setRepositoryData(taskData); + state.setLastReadData(taskData); + state.init(TaskDataManager.this, task); + state.setSaved(false); + state.revert(); + return state; + } + + public ITaskDataWorkingCopy getWorkingCopy(final ITask itask) throws CoreException { + return getWorkingCopy(itask, true); + } + + public ITaskDataWorkingCopy getWorkingCopy(final ITask itask, final boolean markRead) throws CoreException { + final AbstractTask task = (AbstractTask) itask; + Assert.isNotNull(task); + final String kind = task.getConnectorKind(); + final TaskDataState[] result = new TaskDataState[1]; + taskList.run(new ITaskListRunnable() { + public void execute(IProgressMonitor monitor) throws CoreException { + final File file = getMigratedFile(task, kind); + final TaskDataState state = taskDataStore.getTaskDataState(file); + if (state == null) { + throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Task data at \"" //$NON-NLS-1$ + + file + "\" not found")); //$NON-NLS-1$ + } + if (task.isMarkReadPending()) { + state.setLastReadData(state.getRepositoryData()); + } + state.init(TaskDataManager.this, task); + state.revert(); + if (markRead) { + switch (task.getSynchronizationState()) { + case INCOMING: + case INCOMING_NEW: + task.setSynchronizationState(SynchronizationState.SYNCHRONIZED); + break; + case CONFLICT: + task.setSynchronizationState(SynchronizationState.OUTGOING); + break; + } + task.setMarkReadPending(true); + } + result[0] = state; + } + }); + taskList.notifyElementChanged(task); + return result[0]; + } + + public void saveWorkingCopy(final ITask itask, final TaskDataState state) throws CoreException { + final AbstractTask task = (AbstractTask) itask; + Assert.isNotNull(task); + final String kind = task.getConnectorKind(); + taskList.run(new ITaskListRunnable() { + public void execute(IProgressMonitor monitor) throws CoreException { + final File file = getFile(task, kind); + taskDataStore.putTaskData(ensurePathExists(file), state); + switch (task.getSynchronizationState()) { + case SYNCHRONIZED: + task.setSynchronizationState(SynchronizationState.OUTGOING); + } + taskList.addTask(task); + } + }); + taskList.notifyElementChanged(task); + } + + public void putUpdatedTaskData(final ITask itask, final TaskData taskData, final boolean user) throws CoreException { + putUpdatedTaskData(itask, taskData, user, null); + } + + public void putUpdatedTaskData(final ITask itask, final TaskData taskData, final boolean user, Object token) + throws CoreException { + final AbstractTask task = (AbstractTask) itask; + Assert.isNotNull(task); + Assert.isNotNull(taskData); + final AbstractRepositoryConnector connector = repositoryManager.getRepositoryConnector(task.getConnectorKind()); + final TaskRepository repository = repositoryManager.getRepository(task.getConnectorKind(), + task.getRepositoryUrl()); + final boolean taskDataChanged = connector.hasTaskChanged(repository, task, taskData); + final TaskDataManagerEvent event = new TaskDataManagerEvent(this, itask, taskData, token); + event.setTaskDataChanged(taskDataChanged); + final boolean[] synchronizationStateChanged = new boolean[1]; + if (taskDataChanged || user) { + taskList.run(new ITaskListRunnable() { + public void execute(IProgressMonitor monitor) throws CoreException { + if (!taskData.isPartial()) { + File file = getMigratedFile(task, task.getConnectorKind()); + taskDataStore.putTaskData(ensurePathExists(file), taskData, task.isMarkReadPending(), user); + task.setMarkReadPending(false); + event.setTaskDataUpdated(true); + } + + boolean taskChanged = updateTaskFromTaskData(taskData, task, connector, repository); + event.setTaskChanged(taskChanged); + + if (taskDataChanged) { + switch (task.getSynchronizationState()) { + case OUTGOING: + task.setSynchronizationState(SynchronizationState.CONFLICT); + break; + case SYNCHRONIZED: + task.setSynchronizationState(SynchronizationState.INCOMING); + break; + } + } + if (task.isSynchronizing()) { + task.setSynchronizing(false); + synchronizationStateChanged[0] = true; + } + } + }); + } else { + taskList.run(new ITaskListRunnable() { + public void execute(IProgressMonitor monitor) throws CoreException { + if (task.isSynchronizing()) { + task.setSynchronizing(false); + synchronizationStateChanged[0] = true; + } + } + }); + } + if (event.getTaskChanged() || event.getTaskDataChanged()) { + taskList.notifyElementChanged(task); + fireTaskDataUpdated(event); + } else { + if (synchronizationStateChanged[0]) { + taskList.notifySynchronizationStateChanged(task); + } + if (event.getTaskDataUpdated()) { + fireTaskDataUpdated(event); + } + } + } + + private boolean updateTaskFromTaskData(final TaskData taskData, final AbstractTask task, + final AbstractRepositoryConnector connector, final TaskRepository repository) { + task.setChanged(false); + Date oldDueDate = task.getDueDate(); + connector.updateTaskFromTaskData(repository, task, taskData); + // XXX move this to AbstractTask or use model listener to notify task activity + // manager of due date changes + Date newDueDate = task.getDueDate(); + if (oldDueDate != null && !oldDueDate.equals(newDueDate) || newDueDate != oldDueDate) { + taskActivityManager.setDueDate(task, newDueDate); + } + return task.isChanged(); + } + + private File ensurePathExists(File file) { + if (!file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + return file; + } + + private File getMigratedFile(ITask task, String kind) throws CoreException { + Assert.isNotNull(task); + Assert.isNotNull(kind); + File file = getFile(task, kind); + if (!file.exists()) { + File oldFile = getFile10(task, kind); + if (oldFile.exists()) { + TaskDataState state = taskDataStore.getTaskDataState(oldFile); + // save migrated task data right away + taskDataStore.putTaskData(ensurePathExists(file), state); + } + } + return file; + } + + public void discardEdits(final ITask itask) throws CoreException { + final AbstractTask task = (AbstractTask) itask; + Assert.isNotNull(task); + final String kind = task.getConnectorKind(); + taskList.run(new ITaskListRunnable() { + public void execute(IProgressMonitor monitor) throws CoreException { + File dataFile = getFile(task, kind); + if (dataFile.exists()) { + taskDataStore.discardEdits(dataFile); + } + switch (task.getSynchronizationState()) { + case OUTGOING: + task.setSynchronizationState(SynchronizationState.SYNCHRONIZED); + break; + case CONFLICT: + task.setSynchronizationState(SynchronizationState.INCOMING); + break; + } + } + }); + taskList.notifyElementChanged(task); + final TaskDataManagerEvent event = new TaskDataManagerEvent(this, itask); + fireEditsDiscarded(event); + } + + private File findFile(ITask task, String kind) { + File file = getFile(task, kind); + if (file.exists()) { + return file; + } + return getFile10(task, kind); + } + + public String getDataPath() { + return dataPath; + } + + private File getFile(ITask task, String kind) { + return getFile(task.getRepositoryUrl(), task, kind); + } + + private File getFile(String repositoryUrl, ITask task, String kind) { +// String pathName = task.getConnectorKind() + "-" +// + URLEncoder.encode(task.getRepositoryUrl(), ENCODING_UTF_8); +// String fileName = kind + "-" + URLEncoder.encode(task.getTaskId(), ENCODING_UTF_8) + EXTENSION; + String repositoryPath = task.getConnectorKind() + "-" + encode(repositoryUrl); //$NON-NLS-1$ + String fileName = encode(task.getTaskId()) + EXTENSION; + File path = new File(dataPath + File.separator + FOLDER_TASKS + File.separator + repositoryPath + + File.separator + FOLDER_DATA); + return new File(path, fileName); + } + + private static String encode(String text) { + StringBuffer sb = new StringBuffer(text.length()); + char[] chars = text.toCharArray(); + for (char c : chars) { + if (c >= '0' && c <= '9' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '.') { + sb.append(c); + } else { + sb.append("%" + Integer.toHexString(c).toUpperCase()); //$NON-NLS-1$ + } + } + return sb.toString(); + } + + private File getFile10(ITask task, String kind) { + try { + String pathName = URLEncoder.encode(task.getRepositoryUrl(), ENCODING_UTF_8); + String fileName = task.getTaskId() + EXTENSION; + File path = new File(dataPath + File.separator + FOLDER_TASKS_1_0, pathName); + return new File(path, fileName); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + + } + + public TaskData getTaskData(ITask task) throws CoreException { + Assert.isNotNull(task); + final String kind = task.getConnectorKind(); + TaskDataState state = taskDataStore.getTaskDataState(findFile(task, kind)); + if (state == null) { + return null; + } + return state.getRepositoryData(); + } + + public TaskDataState getTaskDataState(ITask task) throws CoreException { + Assert.isNotNull(task); + final String kind = task.getConnectorKind(); + // TODO check that repository task data != null for returned task data state + return taskDataStore.getTaskDataState(findFile(task, kind)); + } + + public TaskData getTaskData(TaskRepository taskRepository, String taskId) throws CoreException { + Assert.isNotNull(taskRepository); + Assert.isNotNull(taskId); + TaskDataState state = taskDataStore.getTaskDataState(findFile(new TaskTask(taskRepository.getConnectorKind(), + taskRepository.getRepositoryUrl(), taskId), taskRepository.getConnectorKind())); + if (state == null) { + return null; + } + return state.getRepositoryData(); + } + + public boolean hasTaskData(ITask task) { + Assert.isNotNull(task); + final String kind = task.getConnectorKind(); + return findFile(task, kind).exists(); + } + + public void putSubmittedTaskData(final ITask itask, final TaskData taskData) throws CoreException { + final AbstractTask task = (AbstractTask) itask; + Assert.isNotNull(task); + Assert.isNotNull(taskData); + final AbstractRepositoryConnector connector = repositoryManager.getRepositoryConnector(task.getConnectorKind()); + final TaskRepository repository = repositoryManager.getRepository(task.getConnectorKind(), + task.getRepositoryUrl()); + final TaskDataManagerEvent event = new TaskDataManagerEvent(this, itask, taskData, null); + event.setTaskDataChanged(true); + taskList.run(new ITaskListRunnable() { + public void execute(IProgressMonitor monitor) throws CoreException { + if (!taskData.isPartial()) { + File file = getMigratedFile(task, task.getConnectorKind()); + taskDataStore.setTaskData(ensurePathExists(file), taskData); + task.setMarkReadPending(false); + event.setTaskDataUpdated(true); + } + + boolean taskChanged = updateTaskFromTaskData(taskData, task, connector, repository); + event.setTaskChanged(taskChanged); + + task.setSynchronizationState(SynchronizationState.SYNCHRONIZED); + task.setSynchronizing(false); + } + }); + taskList.notifyElementChanged(task); + fireTaskDataUpdated(event); + } + + public void deleteTaskData(final ITask itask) throws CoreException { + Assert.isTrue(itask instanceof AbstractTask); + final AbstractTask task = (AbstractTask) itask; + taskList.run(new ITaskListRunnable() { + public void execute(IProgressMonitor monitor) throws CoreException { + File file = getFile(task, task.getConnectorKind()); + if (file.exists()) { + taskDataStore.deleteTaskData(file); + task.setSynchronizationState(SynchronizationState.SYNCHRONIZED); + } + } + }); + taskList.notifyElementChanged(task); + } + + public void setDataPath(String dataPath) { + this.dataPath = dataPath; + } + + /** + * @param task + * repository task to mark as read or unread + * @param read + * true to mark as read, false to mark as unread + */ + public void setTaskRead(final ITask itask, final boolean read) { + final AbstractTask task = (AbstractTask) itask; + Assert.isNotNull(task); + try { + taskList.run(new ITaskListRunnable() { + public void execute(IProgressMonitor monitor) throws CoreException { + if (read) { + switch (task.getSynchronizationState()) { + case INCOMING: + case INCOMING_NEW: + task.setSynchronizationState(SynchronizationState.SYNCHRONIZED); + task.setMarkReadPending(true); + break; + case CONFLICT: + task.setSynchronizationState(SynchronizationState.OUTGOING); + task.setMarkReadPending(true); + break; + } + } else { + switch (task.getSynchronizationState()) { + case SYNCHRONIZED: + task.setSynchronizationState(SynchronizationState.INCOMING); + task.setMarkReadPending(false); + break; + } + } + } + }); + } catch (CoreException e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Unexpected error while marking task read", e)); //$NON-NLS-1$ + } + taskList.notifyElementChanged(task); + } + + void putEdits(final ITask itask, final TaskData editsData) throws CoreException { + final AbstractTask task = (AbstractTask) itask; + Assert.isNotNull(task); + final String kind = task.getConnectorKind(); + Assert.isNotNull(editsData); + taskList.run(new ITaskListRunnable() { + public void execute(IProgressMonitor monitor) throws CoreException { + taskDataStore.putEdits(getFile(task, kind), editsData); + switch (task.getSynchronizationState()) { + case INCOMING: + case INCOMING_NEW: + // TODO throw exception instead? + task.setSynchronizationState(SynchronizationState.CONFLICT); + break; + case SYNCHRONIZED: + task.setSynchronizationState(SynchronizationState.OUTGOING); + break; + } + } + }); + taskList.notifySynchronizationStateChanged(task); + } + + private void fireTaskDataUpdated(final TaskDataManagerEvent event) { + ITaskDataManagerListener[] array = listeners.toArray(new ITaskDataManagerListener[0]); + if (array.length > 0) { + for (final ITaskDataManagerListener listener : array) { + SafeRunner.run(new ISafeRunnable() { + + public void handleException(Throwable exception) { + // ignore + } + + public void run() throws Exception { + listener.taskDataUpdated(event); + } + + }); + } + } + } + + private void fireEditsDiscarded(final TaskDataManagerEvent event) { + ITaskDataManagerListener[] array = listeners.toArray(new ITaskDataManagerListener[0]); + if (array.length > 0) { + for (final ITaskDataManagerListener listener : array) { + SafeRunner.run(new ISafeRunnable() { + + public void handleException(Throwable exception) { + // ignore + } + + public void run() throws Exception { + listener.editsDiscarded(event); + } + + }); + } + } + } + + public void refactorRepositoryUrl(final ITask itask, final String newStorageRepositoryUrl, + final String newRepositoryUrl) throws CoreException { + Assert.isTrue(itask instanceof AbstractTask); + final AbstractTask task = (AbstractTask) itask; + final String kind = task.getConnectorKind(); + taskList.run(new ITaskListRunnable() { + public void execute(IProgressMonitor monitor) throws CoreException { + File file = getMigratedFile(task, kind); + if (file.exists()) { + TaskDataState oldState = taskDataStore.getTaskDataState(file); + if (oldState != null) { + File newFile = getFile(newStorageRepositoryUrl, task, kind); + TaskDataState newState = new TaskDataState(oldState.getConnectorKind(), newRepositoryUrl, + oldState.getTaskId()); + newState.merge(oldState); + taskDataStore.putTaskData(ensurePathExists(newFile), newState); + } + } + } + }); + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataManagerEvent.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataManagerEvent.java new file mode 100644 index 000000000..1cd4196ba --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataManagerEvent.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.data; + +import java.util.EventObject; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.data.ITaskDataManager; +import org.eclipse.mylyn.tasks.core.data.TaskData; + +/** + * @author Steffen Pingel + */ +public class TaskDataManagerEvent extends EventObject { + + private static final long serialVersionUID = 1L; + + private final ITask task; + + private boolean taskChanged; + + private final TaskData taskData; + + private boolean taskDataChanged; + + private boolean taskDataUpdated; + + private final Object token; + + public TaskDataManagerEvent(ITaskDataManager source, ITask task, TaskData taskData, Object token) { + super(source); + Assert.isNotNull(task); + Assert.isNotNull(taskData); + this.task = task; + this.taskData = taskData; + this.token = token; + } + + public TaskDataManagerEvent(ITaskDataManager source, ITask task) { + super(source); + Assert.isNotNull(task); + this.task = task; + this.taskData = null; + this.token = null; + } + + public ITask getTask() { + return task; + } + + public boolean getTaskChanged() { + return taskChanged; + } + + public TaskData getTaskData() { + return taskData; + } + + public boolean getTaskDataChanged() { + return taskDataChanged; + } + + public boolean getTaskDataUpdated() { + return taskDataUpdated; + } + + public Object getToken() { + return token; + } + + public void setTaskChanged(boolean taskChanged) { + this.taskChanged = taskChanged; + } + + public void setTaskDataChanged(boolean taskDataChanged) { + this.taskDataChanged = taskDataChanged; + } + + public void setTaskDataUpdated(boolean taskDataUpdated) { + this.taskDataUpdated = taskDataUpdated; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataState.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataState.java new file mode 100644 index 000000000..016e0f776 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataState.java @@ -0,0 +1,205 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.data; + +import java.util.Set; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.data.ITaskDataWorkingCopy; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskData; + +/** + * @author Rob Elves + * @author Steffen Pingel + */ +public class TaskDataState implements ITaskDataWorkingCopy { + + private final String connectorKind; + + private TaskData editsTaskData; + + private TaskData lastReadTaskData; + + private TaskData localTaskData; + + private boolean saved; + + private TaskData repositoryTaskData; + + private final String repositoryUrl; + + private ITask task; + + private final String taskId; + + private TaskDataManager taskDataManager; + + public TaskDataState(String connectorKind, String repositoryUrl, String taskId) { + Assert.isNotNull(connectorKind); + Assert.isNotNull(repositoryUrl); + Assert.isNotNull(taskId); + this.connectorKind = connectorKind; + this.repositoryUrl = repositoryUrl; + this.taskId = taskId; + this.saved = true; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + TaskDataState other = (TaskDataState) obj; + return connectorKind.equals(other.connectorKind) && repositoryUrl.equals(other.repositoryUrl) + && taskId.equals(other.taskId); + } + + public String getConnectorKind() { + return connectorKind; + } + + public TaskData getEditsData() { + return editsTaskData; + } + + public TaskData getLastReadData() { + return lastReadTaskData; + } + + public TaskData getLocalData() { + return localTaskData; + } + + public TaskData getRepositoryData() { + return repositoryTaskData; + } + + public String getRepositoryUrl() { + return repositoryUrl; + } + + public String getTaskId() { + return taskId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + connectorKind.hashCode(); + result = prime * result + taskId.hashCode(); + result = prime * result + repositoryUrl.hashCode(); + return result; + } + + void init(TaskDataManager taskSynchronizationManager, ITask task) { + this.taskDataManager = taskSynchronizationManager; + this.task = task; + } + + public boolean isSaved() { + return saved; + } + + public void refresh(IProgressMonitor monitor) throws CoreException { + ITaskDataWorkingCopy state = taskDataManager.getWorkingCopy(task); + setRepositoryData(state.getRepositoryData()); + setEditsData(state.getEditsData()); + setLastReadData(state.getLastReadData()); + revert(); + } + + public void revert() { + localTaskData = new TaskData(repositoryTaskData.getAttributeMapper(), repositoryTaskData.getConnectorKind(), + repositoryTaskData.getRepositoryUrl(), repositoryTaskData.getTaskId()); + localTaskData.setVersion(repositoryTaskData.getVersion()); + deepCopyChildren(repositoryTaskData.getRoot(), localTaskData.getRoot()); + if (editsTaskData != null) { + deepCopyChildren(editsTaskData.getRoot(), localTaskData.getRoot()); + } else { + editsTaskData = new TaskData(repositoryTaskData.getAttributeMapper(), + repositoryTaskData.getConnectorKind(), repositoryTaskData.getRepositoryUrl(), + repositoryTaskData.getTaskId()); + editsTaskData.setVersion(repositoryTaskData.getVersion()); + } + } + + private void deepCopyChildren(TaskAttribute source, TaskAttribute target) { + for (TaskAttribute child : source.getAttributes().values()) { + target.deepAddCopy(child); + } + } + + public void save(Set<TaskAttribute> edits, IProgressMonitor monitor) throws CoreException { + if (edits != null) { + for (TaskAttribute edit : edits) { + editsTaskData.getRoot().deepAddCopy(edit); + } + } + if (saved) { + taskDataManager.putEdits(task, editsTaskData); + } else { + taskDataManager.saveWorkingCopy(task, this); + setSaved(true); + } + } + + public void setEditsData(TaskData editsTaskData) { + this.editsTaskData = editsTaskData; + } + + public void setLastReadData(TaskData oldTaskData) { + this.lastReadTaskData = oldTaskData; + } + + public void setLocalTaskData(TaskData localTaskData) { + this.localTaskData = localTaskData; + } + + void setSaved(boolean saved) { + this.saved = saved; + } + + public void setRepositoryData(TaskData newTaskData) { + this.repositoryTaskData = newTaskData; + } + + public void merge(TaskDataState oldState) { + setEditsData(createCopy(oldState.getEditsData())); + setLocalTaskData(createCopy(oldState.getLocalData())); + setRepositoryData(createCopy(oldState.getRepositoryData())); + } + + private TaskData createCopy(TaskData oldData) { + if (oldData == null) { + return null; + } + TaskData newData = new TaskData(oldData.getAttributeMapper(), getConnectorKind(), getRepositoryUrl(), + getTaskId()); + newData.setVersion(oldData.getVersion()); + for (TaskAttribute child : oldData.getRoot().getAttributes().values()) { + newData.getRoot().deepAddCopy(child); + } + return newData; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStateReader.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStateReader.java new file mode 100644 index 000000000..673899425 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStateReader.java @@ -0,0 +1,626 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.data; + +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * @author Steffen Pingel + */ +public class TaskDataStateReader extends DefaultHandler { + + private class AttachmentHandler10 extends ElementHandler { + + private int id; + + private TaskAttribute attribute; + + private final TaskAttribute parentAttribute; + + public AttachmentHandler10(ElementHandler parent, TaskAttribute parentAttribute) { + super(parent, ITaskDataConstants.ELEMENT_ATTACHMENT); + this.parentAttribute = parentAttribute; + } + + @Override + protected void end(String uri, String localName, String name) { + TaskAttribute child = attribute.getAttribute(TaskAttribute.ATTACHMENT_ID); + if (child != null) { + attribute.setValue(child.getValue()); + } + } + + @Override + public void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + // create a unique id for each attachment since the actual id is in a child attribute + attribute = createAttribute(parentAttribute, TaskAttribute.PREFIX_ATTACHMENT + ++id); + attribute.getMetaData().defaults().setReadOnly(true).setType(TaskAttribute.TYPE_ATTACHMENT); + // the actual attachment id is stored in a child node an correctly set in end() + attribute.setValue(id + ""); //$NON-NLS-1$ + + TaskAttribute child = createAttribute(attribute, TaskAttribute.ATTACHMENT_AUTHOR); + child.setValue(getValue(attributes, ITaskDataConstants.ATTRIBUTE_CREATOR)); + child.getMetaData().putValue(TaskAttribute.META_ATTRIBUTE_TYPE, TaskAttribute.TYPE_PERSON); + + child = createAttribute(attribute, TaskAttribute.ATTACHMENT_IS_DEPRECATED); + child.setValue(getValue(attributes, ITaskDataConstants.ATTRIBUTE_IS_OBSOLETE)); + child.getMetaData().putValue(TaskAttribute.META_ATTRIBUTE_TYPE, TaskAttribute.TYPE_BOOLEAN); + + child = createAttribute(attribute, TaskAttribute.ATTACHMENT_IS_PATCH); + child.setValue(getValue(attributes, ITaskDataConstants.ATTRIBUTE_IS_PATCH)); + child.getMetaData().putValue(TaskAttribute.META_ATTRIBUTE_TYPE, TaskAttribute.TYPE_BOOLEAN); + + addElementHandler(new AttributeHandler10(this, attribute) { + @Override + protected String mapId(String value) { + // migrate key for description + if (TaskAttribute.DESCRIPTION.equals(value)) { + return TaskAttribute.ATTACHMENT_DESCRIPTION; + } + return super.mapId(value); + } + }); + } + + } + + private class AttributeHandler10 extends ElementHandler { + + private TaskAttribute attribute; + + private final TaskAttribute parentAttribute; + + public AttributeHandler10(ElementHandler parent, TaskAttribute parentAttribute) { + super(parent, ITaskDataConstants.ELEMENT_ATTRIBUTE); + this.parentAttribute = parentAttribute; + } + + @Override + protected void end(String uri, String localName, String name) { + // detect type + if (attribute.getOptions().size() > 0) { + if (attribute.getValues().size() > 1) { + attribute.getMetaData() + .putValue(TaskAttribute.META_ATTRIBUTE_TYPE, TaskAttribute.TYPE_MULTI_SELECT); + } else { + attribute.getMetaData().putValue(TaskAttribute.META_ATTRIBUTE_TYPE, + TaskAttribute.TYPE_SINGLE_SELECT); + } + } + } + + @Override + public void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + String id = mapId(getValue(attributes, ITaskDataConstants.ATTRIBUTE_ID)); + String label = getValue(attributes, ITaskDataConstants.ATTRIBUTE_NAME); + boolean hidden = Boolean.parseBoolean(getValue(attributes, ITaskDataConstants.ATTRIBUTE_HIDDEN)); + boolean readOnly = Boolean.parseBoolean(getValue(attributes, ITaskDataConstants.ATTRIBUTE_READONLY)); + attribute = parentAttribute.createAttribute(id); + String kind = (hidden) ? null : TaskAttribute.KIND_DEFAULT; + attribute.getMetaData().defaults().setLabel(label).setReadOnly(readOnly).setKind(kind); + + addElementHandler(new OptionHandler10(this, attribute)); + addElementHandler(new ValueHandler10(this, attribute)); + addElementHandler(new MetaDataHandler10(this, attribute)); + } + + protected String mapId(String value) { + return value; + } + + } + + private class AttributeHandler20 extends ElementHandler { + + private TaskAttribute attribute; + + private final TaskAttribute parentAttribute; + + public AttributeHandler20(ElementHandler parent, TaskAttribute parentAttribute) { + super(parent, ITaskDataConstants.ELEMENT_ATTRIBUTE); + this.parentAttribute = parentAttribute; + } + + @Override + public void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + String id = getValue(attributes, ITaskDataConstants.ATTRIBUTE_ID); + attribute = parentAttribute.createAttribute(id); + + addElementHandler(new ValueHandler20(this, attribute)); + addElementHandler(new MapHandler20(this, attribute, ITaskDataConstants.ELEMENT_OPTION)); + addElementHandler(new MapHandler20(this, attribute, ITaskDataConstants.ELEMENT_META)); + addElementHandler(new AttributeHandler20(this, attribute)); + } + + } + + private class CommentHandler10 extends ElementHandler { + + private int id; + + private TaskAttribute attribute; + + private final TaskAttribute parentAttribute; + + public CommentHandler10(ElementHandler parent, TaskAttribute parentAttribute) { + super(parent, ITaskDataConstants.ELEMENT_COMMENT); + this.parentAttribute = parentAttribute; + } + + @Override + protected void end(String uri, String localName, String name) { + TaskAttribute child = attribute.getMappedAttribute(TaskAttribute.COMMENT_TEXT); + if (child != null) { + child.getMetaData().putValue(TaskAttribute.META_READ_ONLY, Boolean.toString(true)); + child.getMetaData().putValue(TaskAttribute.META_ATTRIBUTE_TYPE, TaskAttribute.TYPE_LONG_RICH_TEXT); + } + } + + @Override + public void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + attribute = createAttribute(parentAttribute, TaskAttribute.PREFIX_COMMENT + ++id); + attribute.getMetaData().defaults().setReadOnly(true).setType(TaskAttribute.TYPE_COMMENT); + attribute.getMetaData().putValue(TaskAttribute.META_ASSOCIATED_ATTRIBUTE_ID, TaskAttribute.COMMENT_TEXT); + attribute.setValue(getValue(attributes, ITaskDataConstants.ATTRIBUTE_NUMBER)); + + TaskAttribute child = createAttribute(attribute, TaskAttribute.COMMENT_ATTACHMENT_ID); + child.setValue(getValue(attributes, ITaskDataConstants.ATTRIBUTE_ATTACHMENT_ID)); + + child = createAttribute(attribute, TaskAttribute.COMMENT_HAS_ATTACHMENT); + child.setValue(getValue(attributes, ITaskDataConstants.ATTRIBUTE_HAS_ATTACHMENT)); + child.getMetaData().putValue(TaskAttribute.META_ATTRIBUTE_TYPE, TaskAttribute.TYPE_BOOLEAN); + + child = createAttribute(attribute, TaskAttribute.COMMENT_NUMBER); + child.setValue(getValue(attributes, ITaskDataConstants.ATTRIBUTE_NUMBER)); + child.getMetaData().putValue(TaskAttribute.META_ATTRIBUTE_TYPE, TaskAttribute.TYPE_INTEGER); + + addElementHandler(new AttributeHandler10(this, attribute)); + } + + } + + private class MetaDataHandler10 extends ElementHandler { + + private final TaskAttribute attribute; + + private String key; + + public MetaDataHandler10(ElementHandler parent, TaskAttribute attribute) { + super(parent, ITaskDataConstants.ELEMENT_META); + this.attribute = attribute; + } + + @Override + public void end(String uri, String localName, String name) { + attribute.getMetaData().putValue(key, getCurrentElementText()); + } + + @Override + public void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + key = getValue(attributes, ITaskDataConstants.ATTRIBUTE_KEY); + clearCurrentElementText(); + } + + } + + private class NameHandler extends ElementHandler { + + private final TaskAttribute attribute; + + private String value; + + public NameHandler(ElementHandler parent, TaskAttribute attribute) { + super(parent, ITaskDataConstants.ELEMENT_NAME); + this.attribute = attribute; + } + + @Override + public void end(String uri, String localName, String name) { + attribute.putOption(value, getCurrentElementText()); + } + + @Override + public void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + value = getValue(attributes, ITaskDataConstants.ATTRIBUTE_VALUE); + clearCurrentElementText(); + } + + } + + private class OperationHandler10 extends ElementHandler { + + private TaskAttribute attribute; + + private final TaskAttribute operationAttribute; + + private final TaskAttribute parentAttribute; + + private int id; + + public OperationHandler10(ElementHandler parent, TaskAttribute parentAttribute) { + super(parent, ITaskDataConstants.ELEMENT_OPERATION); + this.parentAttribute = parentAttribute; + this.operationAttribute = createAttribute(parentAttribute, TaskAttribute.OPERATION); + } + + @Override + public void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + attribute = createAttribute(parentAttribute, TaskAttribute.PREFIX_OPERATION + ++id); + attribute.getMetaData().putValue(TaskAttribute.META_ATTRIBUTE_TYPE, TaskAttribute.TYPE_OPERATION); + attribute.getMetaData().putValue(TaskAttribute.META_LABEL, + getValue(attributes, ITaskDataConstants.ATTRIBUTE_OPERATION_NAME)); + String operationId = getValue(attributes, ITaskDataConstants.ATTRIBUTE_KNOB_NAME); + attribute.setValue(operationId); + + if (Boolean.parseBoolean(getValue(attributes, ITaskDataConstants.ATTRIBUTE_IS_CHECKED))) { + operationAttribute.setValue(operationId); + } + + String value = getOptionalValue(attributes, ITaskDataConstants.ATTRIBUTE_OPTION_NAME); + TaskAttribute child; + if (value.length() > 0) { + attribute.getMetaData().putValue(TaskAttribute.META_ASSOCIATED_ATTRIBUTE_ID, value); + child = createAttribute(attribute, value); + child.setValue(getOptionalValue(attributes, ITaskDataConstants.ATTRIBUTE_OPTION_SELECTION)); + child.getMetaData().defaults().setReadOnly(false).setType(TaskAttribute.TYPE_SINGLE_SELECT); + addElementHandler(new NameHandler(this, child)); + } else { + value = getOptionalValue(attributes, ITaskDataConstants.ATTRIBUTE_INPUT_NAME); + if (value.length() > 0) { + attribute.getMetaData().putValue(TaskAttribute.META_ASSOCIATED_ATTRIBUTE_ID, value); + child = createAttribute(attribute, value); + child.setValue(getOptionalValue(attributes, ITaskDataConstants.ATTRIBUTE_INPUT_VALUE)); + child.getMetaData().defaults().setReadOnly(false).setType(TaskAttribute.TYPE_SHORT_TEXT); + } + } + } + + } + + private class OptionHandler10 extends ElementHandler { + + private final TaskAttribute attribute; + + private String parameter; + + public OptionHandler10(ElementHandler parent, TaskAttribute attribute) { + super(parent, ITaskDataConstants.ELEMENT_OPTION); + this.attribute = attribute; + } + + @Override + public void end(String uri, String localName, String name) { + attribute.putOption(parameter, getCurrentElementText()); + } + + @Override + public void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + parameter = getValue(attributes, ITaskDataConstants.ATTRIBUTE_PARAMETER); + clearCurrentElementText(); + } + + } + + private class TaskDataHandler10 extends ElementHandler { + + private TaskData taskData; + + public TaskDataHandler10(TaskStateHandler parent, String elementName) { + super(parent, elementName); + } + + public TaskData getTaskData() { + return taskData; + } + + @Override + public void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + taskData = ((TaskStateHandler) getParent()).createTaskData(attributes); + String taskKind = getOptionalValue(attributes, ITaskDataConstants.ATTRIBUTE_TASK_KIND); + if (taskKind != null) { + createAttribute(taskData.getRoot(), TaskAttribute.TASK_KIND).setValue(taskKind); + } + + addElementHandler(new AttributeHandler10(this, taskData.getRoot())); + addElementHandler(new CommentHandler10(this, taskData.getRoot())); + addElementHandler(new AttachmentHandler10(this, taskData.getRoot())); + addElementHandler(new OperationHandler10(this, taskData.getRoot())); + // the selected operation was never serialized, no need to read it + } + + } + + private class TaskDataHandler20 extends ElementHandler { + + private TaskData taskData; + + public TaskDataHandler20(TaskStateHandler parent, String elementName) { + super(parent, elementName); + } + + public TaskData getTaskData() { + return taskData; + } + + @Override + public void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + taskData = ((TaskStateHandler) getParent()).createTaskData(attributes); + + // skip the root node + ElementHandler handler = new ElementHandler(this, ITaskDataConstants.ELEMENT_ATTRIBUTE); + handler.addElementHandler(new AttributeHandler20(handler, taskData.getRoot())); + addElementHandler(handler); + } + + } + + private class TaskStateHandler extends ElementHandler { + + private TaskAttributeMapper attributeMapper; + + private TaskDataState state; + + private final String version; + + public TaskStateHandler(String version) { + super(null, ITaskDataConstants.ELEMENT_TASK_STATE); + this.version = version; + + if ("1.0".equals(version)) { //$NON-NLS-1$ + addElementHandler(new TaskDataHandler10(this, ITaskDataConstants.ELEMENT_NEW_DATA)); + addElementHandler(new TaskDataHandler10(this, ITaskDataConstants.ELEMENT_OLD_DATA)); + addElementHandler(new TaskDataHandler10(this, ITaskDataConstants.ELEMENT_EDITS_DATA)); + } else if ("2.0".equals(version)) { //$NON-NLS-1$ + addElementHandler(new TaskDataHandler20(this, ITaskDataConstants.ELEMENT_NEW_DATA)); + addElementHandler(new TaskDataHandler20(this, ITaskDataConstants.ELEMENT_OLD_DATA)); + addElementHandler(new TaskDataHandler20(this, ITaskDataConstants.ELEMENT_EDITS_DATA)); + } + } + + public TaskData createTaskData(Attributes attributes) throws SAXException { + TaskData taskData; + if (state == null) { + String connectorKind = getValue(attributes, ITaskDataConstants.ATTRIBUTE_REPOSITORY_KIND); + String repositoryUrl = getValue(attributes, ITaskDataConstants.ATTRIBUTE_REPOSITORY_URL); + String taskId = getValue(attributes, ITaskDataConstants.ATTRIBUTE_ID); + attributeMapper = getAttributeMapper(connectorKind, repositoryUrl); + taskData = new TaskData(attributeMapper, connectorKind, repositoryUrl, taskId); + } else { + taskData = new TaskData(attributeMapper, state.getConnectorKind(), state.getRepositoryUrl(), + state.getTaskId()); + } + String taskDataVersion = getOptionalValue(attributes, ITaskDataConstants.ATTRIBUTE_VERSION); + if (taskDataVersion.length() > 0) { + taskData.setVersion(taskDataVersion); + } + return taskData; + } + + @Override + public void done(ElementHandler elementHandler) { + TaskData taskData; + if (elementHandler instanceof TaskDataHandler10) { + TaskDataHandler10 taskDataHandler = (TaskDataHandler10) elementHandler; + TaskData data = taskDataHandler.getTaskData(); + if (state == null) { + state = new TaskDataState(data.getConnectorKind(), data.getRepositoryUrl(), data.getTaskId()); + } + taskData = taskDataHandler.getTaskData(); + } else { + TaskDataHandler20 taskDataHandler = (TaskDataHandler20) elementHandler; + taskData = taskDataHandler.getTaskData(); + } + + if (ITaskDataConstants.ELEMENT_NEW_DATA.equals(elementHandler.getElementName())) { + state.setRepositoryData(taskData); + } else if (ITaskDataConstants.ELEMENT_OLD_DATA.equals(elementHandler.getElementName())) { + state.setLastReadData(taskData); + } else if (ITaskDataConstants.ELEMENT_EDITS_DATA.equals(elementHandler.getElementName())) { + state.setEditsData(taskData); + } + super.done(elementHandler); + } + + public TaskDataState getState() { + return state; + } + + @Override + protected void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + if ("2.0".equals(version)) { //$NON-NLS-1$ + String connectorKind = getValue(attributes, ITaskDataConstants.ATTRIBUTE_CONNECTOR_KIND); + String repositoryUrl = getValue(attributes, ITaskDataConstants.ATTRIBUTE_REPOSITORY_URL); + String taskId = getValue(attributes, ITaskDataConstants.ATTRIBUTE_TASK_ID); + attributeMapper = getAttributeMapper(connectorKind, repositoryUrl); + state = new TaskDataState(connectorKind, repositoryUrl, taskId); + } + } + } + + private class ValueHandler10 extends ElementHandler { + + private final TaskAttribute attribute; + + public ValueHandler10(ElementHandler parent, TaskAttribute attribute) { + super(parent, ITaskDataConstants.ELEMENT_VALUE); + this.attribute = attribute; + } + + @Override + public void end(String uri, String localName, String name) { + attribute.addValue(getCurrentElementText()); + } + + @Override + protected void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + clearCurrentElementText(); + } + + } + + private class ValueHandler20 extends ElementHandler { + + private final TaskAttribute attribute; + + public ValueHandler20(ElementHandler parent, TaskAttribute attribute) { + super(parent, ITaskDataConstants.ELEMENT_VALUE); + this.attribute = attribute; + } + + @Override + public void end(String uri, String localName, String name) { + attribute.addValue(getCurrentElementText()); + } + + @Override + protected void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + clearCurrentElementText(); + } + + } + + private class MapHandler20 extends ElementHandler { + + private final TaskAttribute attribute; + + private String key = ""; //$NON-NLS-1$ + + private String value = ""; //$NON-NLS-1$ + + public MapHandler20(ElementHandler parent, TaskAttribute attribute, String elementName) { + super(parent, elementName); + this.attribute = attribute; + } + + @Override + public void end(String uri, String localName, String name) { + if (ITaskDataConstants.ELEMENT_OPTION.equals(getElementName())) { + attribute.putOption(key, value); + } else if (ITaskDataConstants.ELEMENT_META.equals(getElementName())) { + attribute.getMetaData().putValue(key, value); + } + key = ""; //$NON-NLS-1$ + value = ""; //$NON-NLS-1$ + } + + @Override + protected void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + addElementHandler(new TextHandler20(this, ITaskDataConstants.ELEMENT_KEY)); + addElementHandler(new TextHandler20(this, ITaskDataConstants.ELEMENT_VALUE)); + } + + @Override + protected void done(ElementHandler handler) { + if (ITaskDataConstants.ELEMENT_KEY.equals(handler.getElementName())) { + key = handler.getCurrentElementText(); + } else if (ITaskDataConstants.ELEMENT_VALUE.equals(handler.getElementName())) { + value = handler.getCurrentElementText(); + } + super.done(handler); + } + + } + + private class TextHandler20 extends ElementHandler { + + public TextHandler20(ElementHandler parent, String elementName) { + super(parent, elementName); + } + + @Override + protected void start(String uri, String localName, String name, Attributes attributes) throws SAXException { + clearCurrentElementText(); + } + + } + + private TaskStateHandler handler; + + private final IRepositoryManager repositoryManager; + + private TaskDataState result; + + public TaskDataStateReader(IRepositoryManager repositoryManager) { + this.repositoryManager = repositoryManager; + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if (handler != null) { + handler.characters(ch, start, length); + } + } + + private TaskAttribute createAttribute(TaskAttribute parent, String id) { + TaskAttribute attribute = parent.createAttribute(id); + attribute.getMetaData().defaults(); + return attribute; + } + + @Override + public void endElement(String uri, String localName, String name) throws SAXException { + if (handler != null) { + handler.endElement(uri, localName, name); + if (ITaskDataConstants.ELEMENT_TASK_STATE.equals(name)) { + result = handler.getState(); + handler = null; + } + } + } + + private TaskAttributeMapper getAttributeMapper(String connectorKind, String repositoryUrl) throws SAXException { + AbstractRepositoryConnector connector = repositoryManager.getRepositoryConnector(connectorKind); + if (connector == null) { + throw new SAXException("No repository connector for kind \"" + connectorKind + "\" found"); //$NON-NLS-1$ //$NON-NLS-2$ + } + + TaskRepository taskRepository = repositoryManager.getRepository(connectorKind, repositoryUrl); + if (taskRepository == null) { + throw new SAXException("Repository \"" + repositoryUrl + "\" not found for kind \"" + connectorKind + "\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + final TaskAttributeMapper attributeMapper; + AbstractTaskDataHandler taskDataHandler = connector.getTaskDataHandler(); + if (taskDataHandler != null) { + attributeMapper = taskDataHandler.getAttributeMapper(taskRepository); + } else { + attributeMapper = new TaskAttributeMapper(taskRepository); + } + return attributeMapper; + } + + public TaskDataState getTaskDataState() { + return result; + } + + @Override + public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { + if (handler != null) { + handler.startElement(uri, localName, name, attributes); + } + if (ITaskDataConstants.ELEMENT_TASK_STATE.equals(name)) { + String version = attributes.getValue(ITaskDataConstants.ATTRIBUTE_VERSION); + handler = new TaskStateHandler(version); + handler.start(uri, localName, name, attributes); + } + } + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStateWriter.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStateWriter.java new file mode 100644 index 000000000..0139ac416 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStateWriter.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.data; + +import java.util.List; +import java.util.Map; + +import javax.xml.transform.sax.TransformerHandler; + +import org.eclipse.mylyn.tasks.core.data.ITaskDataWorkingCopy; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +/** + * @author Steffen Pingel + */ +public class TaskDataStateWriter { + + private static final String TASK_DATA_STATE_VERSION = "2.0"; //$NON-NLS-1$ + + private static final String CDATA = "CDATA"; //$NON-NLS-1$ + + private final TransformerHandler handler; + + public TaskDataStateWriter(TransformerHandler handler) { + this.handler = handler; + } + + public void write(ITaskDataWorkingCopy state) throws SAXException { + handler.startDocument(); + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "", ITaskDataConstants.ATTRIBUTE_CONNECTOR_KIND, CDATA, state.getConnectorKind()); //$NON-NLS-1$ //$NON-NLS-2$ + atts.addAttribute("", "", ITaskDataConstants.ATTRIBUTE_REPOSITORY_URL, CDATA, state.getRepositoryUrl()); //$NON-NLS-1$ //$NON-NLS-2$ + atts.addAttribute("", "", ITaskDataConstants.ATTRIBUTE_TASK_ID, CDATA, state.getTaskId()); //$NON-NLS-1$ //$NON-NLS-2$ + atts.addAttribute("", "", ITaskDataConstants.ATTRIBUTE_VERSION, CDATA, TASK_DATA_STATE_VERSION); //$NON-NLS-1$ //$NON-NLS-2$ + handler.startElement("", "", ITaskDataConstants.ELEMENT_TASK_STATE, atts); //$NON-NLS-1$ //$NON-NLS-2$ + if (state.getRepositoryData() != null) { + writeTaskData(state.getRepositoryData(), ITaskDataConstants.ELEMENT_NEW_DATA); + } + if (state.getLastReadData() != null) { + writeTaskData(state.getLastReadData(), ITaskDataConstants.ELEMENT_OLD_DATA); + } + if (state.getEditsData() != null) { + writeTaskData(state.getEditsData(), ITaskDataConstants.ELEMENT_EDITS_DATA); + } + handler.endElement("", "", ITaskDataConstants.ELEMENT_TASK_STATE); //$NON-NLS-1$ //$NON-NLS-2$ + handler.endDocument(); + } + + private void writeTaskData(TaskData taskData, String elementName) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "", ITaskDataConstants.ATTRIBUTE_CONNECTOR_KIND, CDATA, taskData.getConnectorKind()); //$NON-NLS-1$ //$NON-NLS-2$ + atts.addAttribute("", "", ITaskDataConstants.ATTRIBUTE_REPOSITORY_URL, CDATA, taskData.getRepositoryUrl()); //$NON-NLS-1$ //$NON-NLS-2$ + atts.addAttribute("", "", ITaskDataConstants.ATTRIBUTE_TASK_ID, CDATA, taskData.getTaskId()); //$NON-NLS-1$ //$NON-NLS-2$ + if (taskData.getVersion() != null) { + atts.addAttribute("", "", ITaskDataConstants.ATTRIBUTE_VERSION, CDATA, taskData.getVersion()); //$NON-NLS-1$ //$NON-NLS-2$ + } + handler.startElement("", "", elementName, atts); //$NON-NLS-1$ //$NON-NLS-2$ + atts.clear(); + handler.startElement("", "", ITaskDataConstants.ELEMENT_ATTRIBUTES, atts); //$NON-NLS-1$ //$NON-NLS-2$ + writeTaskAttribute(taskData.getRoot()); + handler.endElement("", "", ITaskDataConstants.ELEMENT_ATTRIBUTES); //$NON-NLS-1$ //$NON-NLS-2$ + handler.endElement("", "", elementName); //$NON-NLS-1$ //$NON-NLS-2$ + } + + private void writeTaskAttribute(TaskAttribute attribute) throws SAXException { + AttributesImpl atts = new AttributesImpl(); + atts.addAttribute("", "", ITaskDataConstants.ATTRIBUTE_ID, CDATA, attribute.getId()); //$NON-NLS-1$ //$NON-NLS-2$ + handler.startElement("", "", ITaskDataConstants.ELEMENT_ATTRIBUTE, atts); //$NON-NLS-1$ //$NON-NLS-2$ + atts.clear(); + + handler.startElement("", "", ITaskDataConstants.ELEMENT_VALUES, atts); //$NON-NLS-1$ //$NON-NLS-2$ + List<String> values = attribute.getValues(); + for (String value : values) { + handler.startElement("", "", ITaskDataConstants.ELEMENT_VALUE, atts); //$NON-NLS-1$ //$NON-NLS-2$ + handler.characters(value.toCharArray(), 0, value.length()); + handler.endElement("", "", ITaskDataConstants.ELEMENT_VALUE); //$NON-NLS-1$ //$NON-NLS-2$ + } + handler.endElement("", "", ITaskDataConstants.ELEMENT_VALUES); //$NON-NLS-1$ //$NON-NLS-2$ + + handler.startElement("", "", ITaskDataConstants.ELEMENT_OPTIONS, atts); //$NON-NLS-1$ //$NON-NLS-2$ + writeMap(atts, attribute.getOptions(), ITaskDataConstants.ELEMENT_OPTION); + handler.endElement("", "", ITaskDataConstants.ELEMENT_OPTIONS); //$NON-NLS-1$ //$NON-NLS-2$ + + handler.startElement("", "", ITaskDataConstants.ELEMENT_META_DATA, atts); //$NON-NLS-1$ //$NON-NLS-2$ + writeMap(atts, attribute.getMetaData().getValues(), ITaskDataConstants.ELEMENT_META); + handler.endElement("", "", ITaskDataConstants.ELEMENT_META_DATA); //$NON-NLS-1$ //$NON-NLS-2$ + + handler.startElement("", "", ITaskDataConstants.ELEMENT_ATTRIBUTES, atts); //$NON-NLS-1$ //$NON-NLS-2$ + Map<String, TaskAttribute> children = attribute.getAttributes(); + for (TaskAttribute child : children.values()) { + writeTaskAttribute(child); + } + handler.endElement("", "", ITaskDataConstants.ELEMENT_ATTRIBUTES); //$NON-NLS-1$ //$NON-NLS-2$ + + handler.endElement("", "", ITaskDataConstants.ELEMENT_ATTRIBUTE); //$NON-NLS-1$ //$NON-NLS-2$ + } + + private void writeMap(AttributesImpl atts, Map<String, String> options, String elementName) throws SAXException { + for (String key : options.keySet()) { + String value = options.get(key); + handler.startElement("", "", elementName, atts); //$NON-NLS-1$ //$NON-NLS-2$ + handler.startElement("", "", ITaskDataConstants.ELEMENT_KEY, atts); //$NON-NLS-1$ //$NON-NLS-2$ + handler.characters(key.toCharArray(), 0, key.length()); + handler.endElement("", "", ITaskDataConstants.ELEMENT_KEY); //$NON-NLS-1$ //$NON-NLS-2$ + handler.startElement("", "", ITaskDataConstants.ELEMENT_VALUE, atts); //$NON-NLS-1$ //$NON-NLS-2$ + handler.characters(value.toCharArray(), 0, value.length()); + handler.endElement("", "", ITaskDataConstants.ELEMENT_VALUE); //$NON-NLS-1$ //$NON-NLS-2$ + handler.endElement("", "", elementName); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStore.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStore.java new file mode 100644 index 000000000..9dd1e692b --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStore.java @@ -0,0 +1,166 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.data; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.mylyn.tasks.core.data.TaskData; + +/** + * @author Steffen Pingel + */ +public class TaskDataStore { + + private static final String FILE_NAME_INTERNAL = "data.xml"; //$NON-NLS-1$ + + private final TaskDataExternalizer externalizer; + + public TaskDataStore(IRepositoryManager taskRepositoryManager) { + this.externalizer = new TaskDataExternalizer(taskRepositoryManager); + } + + public synchronized TaskDataState discardEdits(File file) throws CoreException { + TaskDataState state = readState(file); + if (state != null) { + state.setEditsData(null); + } + writeState(file, state); + return state; + } + + public synchronized TaskDataState getTaskDataState(File file) throws CoreException { + return readState(file); + } + + public synchronized void putEdits(File file, TaskData data) throws CoreException { + Assert.isNotNull(file); + Assert.isNotNull(data); + TaskDataState state = readState(file); + if (state == null) { + state = new TaskDataState(data.getConnectorKind(), data.getRepositoryUrl(), data.getTaskId()); + } + state.setEditsData(data); + writeState(file, state); + } + + public synchronized void putTaskData(File file, TaskData data, boolean setLastRead, boolean user) + throws CoreException { + Assert.isNotNull(file); + Assert.isNotNull(data); + TaskDataState state = null; + try { + state = readState(file); + } catch (CoreException e) { + if (!user) { + throw new CoreException( + new Status( + IStatus.ERROR, + ITasksCoreConstants.ID_PLUGIN, + "Reading of existing task data failed. Forcing synchronization will override outgoing changes.", //$NON-NLS-1$ + e)); + } + } + if (state == null) { + state = new TaskDataState(data.getConnectorKind(), data.getRepositoryUrl(), data.getTaskId()); + } + if (setLastRead) { + state.setLastReadData(state.getRepositoryData()); + } + state.setRepositoryData(data); + writeState(file, state); + } + + public synchronized void setTaskData(File file, TaskData data) throws CoreException { + Assert.isNotNull(file); + Assert.isNotNull(data); + + // TODO consider reading old task data and compare submitted results to check if all outgoing changes were accepted by repository + + TaskDataState state = new TaskDataState(data.getConnectorKind(), data.getRepositoryUrl(), data.getTaskId()); + state.setRepositoryData(data); + state.setEditsData(null); + state.setLastReadData(data); + writeState(file, state); + } + + private TaskDataState readState(File file) throws CoreException { + try { + if (file.exists()) { + ZipInputStream in = new ZipInputStream(new BufferedInputStream(new FileInputStream(file))); + try { + in.getNextEntry(); + return externalizer.readState(in); + } finally { + in.close(); + } + } + return null; + } catch (IOException e) { + throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Error reading task data", //$NON-NLS-1$ + e)); + } + } + + private void writeState(File file, TaskDataState state) throws CoreException { + try { + ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(file))); + try { + out.setMethod(ZipOutputStream.DEFLATED); + + ZipEntry entry = new ZipEntry(FILE_NAME_INTERNAL); + out.putNextEntry(entry); + + externalizer.writeState(out, state); + } finally { + out.close(); + } + } catch (IOException e) { + throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Error writing task data", //$NON-NLS-1$ + e)); + } + } + +// public synchronized void putLastRead(File file, TaskData data) throws CoreException { +// Assert.isNotNull(file); +// Assert.isNotNull(data); +// +// TaskDataState state = readState(file); +// if (state == null) { +// state = new TaskDataState(data.getConnectorKind(), data.getRepositoryUrl(), data.getTaskId()); +// } +// state.setLastReadData(data); +// writeState(file, state); +// } + + public synchronized void putTaskData(File file, TaskDataState state) throws CoreException { + writeState(file, state); + } + + public synchronized boolean deleteTaskData(File file) { + return file.delete(); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TextTaskAttachmentSource.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TextTaskAttachmentSource.java new file mode 100644 index 000000000..7f1602c45 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TextTaskAttachmentSource.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.data; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskAttachmentSource; + +public class TextTaskAttachmentSource extends AbstractTaskAttachmentSource { + + private final String contents; + + public TextTaskAttachmentSource(String contents) { + this.contents = contents; + } + + @Override + public InputStream createInputStream(IProgressMonitor monitor) throws CoreException { + return new ByteArrayInputStream(contents.getBytes()); + } + + @Override + public String getContentType() { + return "text/plain"; //$NON-NLS-1$ + } + + @Override + public String getDescription() { + return ""; //$NON-NLS-1$ + } + + @Override + public long getLength() { + return contents.getBytes().length; + } + + @Override + public String getName() { + return "clipboard.txt"; //$NON-NLS-1$ + } + + @Override + public boolean isLocal() { + return true; + } + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/AbstractExternalizationParticipant.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/AbstractExternalizationParticipant.java new file mode 100644 index 000000000..f43006dd8 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/AbstractExternalizationParticipant.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.externalization; + +import java.io.File; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.commons.net.Policy; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; + +/** + * File based externalization participant + * + * @author Rob Elves + */ +public abstract class AbstractExternalizationParticipant implements IExternalizationParticipant { + + public static final String SNAPSHOT_PREFIX = "."; //$NON-NLS-1$ + + public abstract void load(File sourceFile, IProgressMonitor monitor) throws CoreException; + + public abstract void save(File targetFile, IProgressMonitor monitor) throws CoreException; + + public abstract String getDescription(); + + public abstract ISchedulingRule getSchedulingRule(); + + public abstract boolean isDirty(); + + public abstract String getFileName(); + + public AbstractExternalizationParticipant() { + super(); + } + + protected boolean takeSnapshot(File file) { + if (file.length() > 0) { + File originalFile = file.getAbsoluteFile(); + File backup = new File(file.getParentFile(), SNAPSHOT_PREFIX + file.getName()); + backup.delete(); + return originalFile.renameTo(backup); + } + return false; + } + + public void execute(IExternalizationContext context, IProgressMonitor monitor) throws CoreException { + Assert.isNotNull(context); + monitor = Policy.monitorFor(monitor); + final File dataFile = getFile(context.getRootPath()); + switch (context.getKind()) { + case SAVE: + if (dataFile != null) { + takeSnapshot(dataFile); + } + save(dataFile, monitor); + break; + case LOAD: + performLoad(dataFile, monitor); + break; + case SNAPSHOT: + break; + } + + } + + protected boolean performLoad(final File dataFile, IProgressMonitor monitor) throws CoreException { + try { + load(dataFile, monitor); + return true; + } catch (CoreException e) { + if (dataFile != null) { + File backup = new File(dataFile.getParentFile(), SNAPSHOT_PREFIX + dataFile.getName()); + if (backup.exists()) { + StatusHandler.log(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Failed to load " //$NON-NLS-1$ + + dataFile.getName() + ", restoring from snapshot", e)); //$NON-NLS-1$ + load(backup, monitor); + return true; + } + } + } + return false; + } + + public File getFile(String rootPath) throws CoreException { + String fileName = getFileName(); + if (fileName != null) { + String filePath = rootPath + File.separator + getFileName(); + return new File(filePath); + } + + return null; + } + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/DelegatingTaskExternalizer.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/DelegatingTaskExternalizer.java new file mode 100644 index 000000000..2ca1125b1 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/DelegatingTaskExternalizer.java @@ -0,0 +1,805 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + * Ken Sueda - XML serialization support + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.externalization; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.ISafeRunnable; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.internal.tasks.core.AbstractTask; +import org.eclipse.mylyn.internal.tasks.core.AbstractTaskCategory; +import org.eclipse.mylyn.internal.tasks.core.AbstractTaskContainer; +import org.eclipse.mylyn.internal.tasks.core.DayDateRange; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.internal.tasks.core.ITransferList; +import org.eclipse.mylyn.internal.tasks.core.LocalRepositoryConnector; +import org.eclipse.mylyn.internal.tasks.core.LocalTask; +import org.eclipse.mylyn.internal.tasks.core.RepositoryModel; +import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery; +import org.eclipse.mylyn.internal.tasks.core.RepositoryTaskHandleUtil; +import org.eclipse.mylyn.internal.tasks.core.TaskActivityUtil; +import org.eclipse.mylyn.internal.tasks.core.TaskCategory; +import org.eclipse.mylyn.internal.tasks.core.TaskExternalizationException; +import org.eclipse.mylyn.internal.tasks.core.TaskTask; +import org.eclipse.mylyn.internal.tasks.core.UncategorizedTaskContainer; +import org.eclipse.mylyn.internal.tasks.core.WeekDateRange; +import org.eclipse.mylyn.tasks.core.AbstractTaskListMigrator; +import org.eclipse.mylyn.tasks.core.IAttributeContainer; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.mylyn.tasks.core.IRepositoryQuery; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.ITask.PriorityLevel; +import org.eclipse.mylyn.tasks.core.ITask.SynchronizationState; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * Subclass externalizers must override the get*TagName() methods for the types of externalized items they support to + * ensure that their externalizer does not externalize tasks from other connectors incorrectly. + * + * These tag names uniquely identify the externalizer to be used to read the task from externalized form on disk. + * + * The canCreateElementFor methods specify which tasks the externalizer should write to disk. + * + * The TaskList is read on startup, so externalizers extending this should not perform any slow (i.e., network) + * operations when overriding methods. + * + * @author Mik Kersten + * @author Steffen Pingel + */ +public final class DelegatingTaskExternalizer { + + static final String DEFAULT_PRIORITY = PriorityLevel.P3.toString(); + + static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.S z"; //$NON-NLS-1$ + + static final String KEY_NOTIFIED_INCOMING = "NotifiedIncoming"; //$NON-NLS-1$ + + public static final String KEY_NAME = "Name"; //$NON-NLS-1$ + + public static final String KEY_LABEL = "Label"; //$NON-NLS-1$ + + public static final String KEY_QUERY = "Query"; //$NON-NLS-1$ + + public static final String KEY_QUERY_STRING = "QueryString"; //$NON-NLS-1$ + + static final String KEY_HANDLE = "Handle"; //$NON-NLS-1$ + + public static final String KEY_REPOSITORY_URL = "RepositoryUrl"; //$NON-NLS-1$ + + public static final String KEY_CATEGORY = "Category"; //$NON-NLS-1$ + + static final String VAL_ROOT = "Root"; //$NON-NLS-1$ + + static final String KEY_SUBTASK = "SubTask"; //$NON-NLS-1$ + + static final String KEY_KIND = "Kind"; //$NON-NLS-1$ + + static final String KEY_TASK_CATEGORY = "Task" + KEY_CATEGORY; //$NON-NLS-1$ + + static final String KEY_LINK = "Link"; //$NON-NLS-1$ + + static final String KEY_PLAN = "Plan"; //$NON-NLS-1$ + + static final String KEY_TIME_ESTIMATED = "Estimated"; //$NON-NLS-1$ + + static final String KEY_ISSUEURL = "IssueURL"; //$NON-NLS-1$ + + static final String KEY_NOTES = "Notes"; //$NON-NLS-1$ + + static final String KEY_ACTIVE = "Active"; //$NON-NLS-1$ + + static final String KEY_PRIORITY = "Priority"; //$NON-NLS-1$ + + static final String KEY_PATH = "Path"; //$NON-NLS-1$ + + static final String VAL_FALSE = "false"; //$NON-NLS-1$ + + static final String VAL_TRUE = "true"; //$NON-NLS-1$ + + static final String KEY_DATE_END = "EndDate"; //$NON-NLS-1$ + + static final String KEY_QUERY_HIT = "QueryHit"; //$NON-NLS-1$ + + static final String KEY_TASK_REFERENCE = "TaskReference"; //$NON-NLS-1$ + + static final String KEY_DATE_CREATION = "CreationDate"; //$NON-NLS-1$ + + static final String KEY_DATE_REMINDER = "ReminderDate"; //$NON-NLS-1$ + + static final String KEY_DATE_SCHEDULED_START = "ScheduledStartDate"; //$NON-NLS-1$ + + static final String KEY_DATE_SCHEDULED_END = "ScheduledEndDate"; //$NON-NLS-1$ + + static final String KEY_DATE_MODIFICATION = "ModificationDate"; //$NON-NLS-1$ + + static final String KEY_DATE_DUE = "DueDate"; //$NON-NLS-1$ + + static final String KEY_REMINDED = "Reminded"; //$NON-NLS-1$ + + static final String KEY_FLOATING = "Floating"; //$NON-NLS-1$ + + /** + * This element holds the date stamp recorded upon last transition to a synchronized state. + */ + static final String KEY_LAST_MOD_DATE = "LastModified"; //$NON-NLS-1$ + + static final String KEY_DIRTY = "Dirty"; //$NON-NLS-1$ + + static final String KEY_SYNC_STATE = "offlineSyncState"; //$NON-NLS-1$ + + static final String KEY_OWNER = "Owner"; //$NON-NLS-1$ + + static final String KEY_MARK_READ_PENDING = "MarkReadPending"; //$NON-NLS-1$ + + static final String KEY_STALE = "Stale"; //$NON-NLS-1$ + + static final String KEY_CONNECTOR_KIND = "ConnectorKind"; //$NON-NLS-1$ + + static final String KEY_TASK_ID = "TaskId"; //$NON-NLS-1$ + + public static final String KEY_LAST_REFRESH = "LastRefreshTimeStamp"; //$NON-NLS-1$ + + static final String NODE_ATTRIBUTE = "Attribute"; //$NON-NLS-1$ + + static final String NODE_QUERY = "Query"; //$NON-NLS-1$ + + static final String NODE_TASK = "Task"; //$NON-NLS-1$ + + static final String KEY_KEY = "Key"; //$NON-NLS-1$ + + // 2.0 -> 3.0 migration holds tasks to category handles + private final Map<AbstractTask, String> parentCategoryMap; + + private final RepositoryModel repositoryModel; + + private List<AbstractTaskListMigrator> migrators; + + private boolean taskActivated; + + private final IRepositoryManager repositoryManager; + + private final List<IStatus> errors; + + public DelegatingTaskExternalizer(RepositoryModel repositoryModel, IRepositoryManager repositoryManager) { + Assert.isNotNull(repositoryModel); + Assert.isNotNull(repositoryManager); + this.repositoryModel = repositoryModel; + this.repositoryManager = repositoryManager; + this.parentCategoryMap = new HashMap<AbstractTask, String>(); + this.errors = new ArrayList<IStatus>(); + this.migrators = Collections.emptyList(); + } + + public void initialize(List<AbstractTaskListMigrator> migrators) { + Assert.isNotNull(migrators); + this.migrators = migrators; + } + + public Element createCategoryElement(AbstractTaskCategory category, Document doc, Element parent) { + Element node = doc.createElement(getCategoryTagName()); + node.setAttribute(DelegatingTaskExternalizer.KEY_HANDLE, category.getHandleIdentifier()); + node.setAttribute(DelegatingTaskExternalizer.KEY_NAME, category.getSummary()); + parent.appendChild(node); + for (ITask task : category.getChildren()) { + createTaskReference(KEY_TASK_REFERENCE, task, doc, node); + } + return node; + } + + @SuppressWarnings("deprecation") + public Element createTaskElement(final AbstractTask task, Document doc, Element parent) { + final Element node; + if (task.getClass() == TaskTask.class || task instanceof LocalTask) { + node = doc.createElement(NODE_TASK); + } else { + errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "No externalizer for task: " + task)); //$NON-NLS-1$ + return null; + } + + node.setAttribute(KEY_CONNECTOR_KIND, task.getConnectorKind()); + node.setAttribute(KEY_REPOSITORY_URL, task.getRepositoryUrl()); + node.setAttribute(KEY_TASK_ID, task.getTaskId()); + if (task.getTaskKey() != null) { + node.setAttribute(KEY_KEY, task.getTaskKey()); + } + node.setAttribute(KEY_HANDLE, task.getHandleIdentifier()); + node.setAttribute(KEY_LABEL, stripControlCharacters(task.getSummary())); + + node.setAttribute(KEY_PRIORITY, task.getPriority()); + node.setAttribute(KEY_KIND, task.getTaskKind()); + + if (task.isActive()) { + node.setAttribute(KEY_ACTIVE, VAL_TRUE); + } else { + node.setAttribute(KEY_ACTIVE, VAL_FALSE); + } + + if (task.getUrl() != null) { + node.setAttribute(KEY_ISSUEURL, task.getUrl()); + } + node.setAttribute(KEY_NOTES, stripControlCharacters(task.getNotes())); + node.setAttribute(KEY_TIME_ESTIMATED, "" + task.getEstimatedTimeHours()); //$NON-NLS-1$ + node.setAttribute(KEY_DATE_END, formatExternDate(task.getCompletionDate())); + node.setAttribute(KEY_DATE_CREATION, formatExternDate(task.getCreationDate())); + node.setAttribute(KEY_DATE_MODIFICATION, formatExternDate(task.getModificationDate())); + node.setAttribute(KEY_DATE_DUE, formatExternDate(task.getDueDate())); + if (task.getScheduledForDate() != null) { + node.setAttribute(KEY_DATE_SCHEDULED_START, formatExternCalendar(task.getScheduledForDate().getStartDate())); + node.setAttribute(KEY_DATE_SCHEDULED_END, formatExternCalendar(task.getScheduledForDate().getEndDate())); + } + if (task.isReminded()) { + node.setAttribute(KEY_REMINDED, VAL_TRUE); + } else { + node.setAttribute(KEY_REMINDED, VAL_FALSE); + } + if (task.isStale()) { + node.setAttribute(KEY_STALE, VAL_TRUE); + } else { + node.setAttribute(KEY_STALE, VAL_FALSE); + } + if (task.isMarkReadPending()) { + node.setAttribute(KEY_MARK_READ_PENDING, VAL_TRUE); + } else { + node.setAttribute(KEY_MARK_READ_PENDING, VAL_FALSE); + } + if (task.getLastReadTimeStamp() != null) { + node.setAttribute(KEY_LAST_MOD_DATE, task.getLastReadTimeStamp()); + } + if (task.isNotified()) { + node.setAttribute(KEY_NOTIFIED_INCOMING, VAL_TRUE); + } else { + node.setAttribute(KEY_NOTIFIED_INCOMING, VAL_FALSE); + } + if (task.getSynchronizationState() != null) { + node.setAttribute(KEY_SYNC_STATE, task.getSynchronizationState().name()); + } else { + node.setAttribute(KEY_SYNC_STATE, SynchronizationState.SYNCHRONIZED.name()); + } + if (task.getOwner() != null) { + node.setAttribute(KEY_OWNER, task.getOwner()); + } + createAttributes(task, doc, node); + for (ITask t : task.getChildren()) { + createTaskReference(KEY_SUBTASK, t, doc, node); + } + + parent.appendChild(node); + return node; + } + + private void createAttributes(IAttributeContainer container, Document doc, Element parent) { + Map<String, String> attributes = container.getAttributes(); + for (Map.Entry<String, String> entry : attributes.entrySet()) { + Element node = doc.createElement(NODE_ATTRIBUTE); + node.setAttribute(KEY_KEY, entry.getKey()); + node.setTextContent(entry.getValue()); + parent.appendChild(node); + } + + } + + /** + * creates nested task reference nodes named nodeName which include a handle to the task + * + * @return + */ + public Element createTaskReference(String nodeName, ITask task, Document doc, Element parent) { + Element node = doc.createElement(nodeName); + node.setAttribute(KEY_HANDLE, task.getHandleIdentifier()); + parent.appendChild(node); + return node; + } + + /** + * create tasks from the nodes provided and places them within the given container + */ + public void readTaskReferences(AbstractTaskContainer task, NodeList nodes, ITransferList tasklist) { + for (int j = 0; j < nodes.getLength(); j++) { + Node child = nodes.item(j); + Element element = (Element) child; + if (element.hasAttribute(KEY_HANDLE)) { + String handle = element.getAttribute(KEY_HANDLE); + AbstractTask subTask = tasklist.getTask(handle); + if (subTask != null) { + tasklist.addTask(subTask, task); + } else { + errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, + "Failed to add subtask with handle \"" + handle + "\" to \"" + task + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + } + } + } + + @SuppressWarnings( { "restriction" }) + private String stripControlCharacters(String text) { + if (text == null) { + return ""; //$NON-NLS-1$ + } + return org.eclipse.mylyn.internal.commons.core.XmlStringConverter.cleanXmlString(text); + } + + private String formatExternDate(Date date) { + if (date == null) { + return ""; //$NON-NLS-1$ + } + String f = DATE_FORMAT; + SimpleDateFormat format = new SimpleDateFormat(f, Locale.ENGLISH); + return format.format(date); + } + + private String formatExternCalendar(Calendar date) { + if (date == null) { + return ""; //$NON-NLS-1$ + } + String f = DATE_FORMAT; + SimpleDateFormat format = new SimpleDateFormat(f, Locale.ENGLISH); + return format.format(date.getTime()); + } + + public void readCategory(Node node, ITransferList taskList) { + Element element = (Element) node; + AbstractTaskCategory category = null; + if (element.hasAttribute(KEY_NAME)) { + String name = element.getAttribute(KEY_NAME); + String handle = name; + if (element.hasAttribute(KEY_HANDLE)) { + handle = element.getAttribute(KEY_HANDLE); + } + category = taskList.getContainerForHandle(handle); + if (category == null) { + category = new TaskCategory(handle, name); + taskList.addCategory((TaskCategory) category); + } else if (!UncategorizedTaskContainer.HANDLE.equals(handle)) { + errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Category with handle \"" + name //$NON-NLS-1$ + + "\" already exists in task list")); //$NON-NLS-1$ + } + } else { + errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Category is missing name attribute")); //$NON-NLS-1$ + // LEGACY: registry categories did not have names + // category = taskList.getArchiveContainer(); + // a null category will now go into appropriate orphaned category + } + + NodeList list = node.getChildNodes(); + readTaskReferences(category, list, taskList); + } + + @SuppressWarnings("deprecation") + public final AbstractTask readTask(Node node, AbstractTaskCategory legacyCategory, ITask parent) + throws CoreException { + String handle; + String taskId; + String repositoryUrl; + String summary = ""; //$NON-NLS-1$ + final Element element = (Element) node; + if (element.hasAttribute(KEY_REPOSITORY_URL) && element.hasAttribute(KEY_TASK_ID) + && element.hasAttribute(KEY_HANDLE)) { + handle = element.getAttribute(KEY_HANDLE); + repositoryUrl = element.getAttribute(KEY_REPOSITORY_URL); + taskId = element.getAttribute(KEY_TASK_ID); + } else if (element.hasAttribute(KEY_HANDLE)) { + handle = element.getAttribute(KEY_HANDLE); + repositoryUrl = RepositoryTaskHandleUtil.getRepositoryUrl(handle); + taskId = RepositoryTaskHandleUtil.getTaskId(handle); + } else { + errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Task is missing handle attribute")); //$NON-NLS-1$ + return null; + } + if (element.hasAttribute(KEY_LABEL)) { + summary = element.getAttribute(KEY_LABEL); + } + + AbstractTask task = null; + AbstractTaskListMigrator taskMigrator = null; + if (NODE_TASK.equals(node.getNodeName())) { + String connectorKind = element.getAttribute(DelegatingTaskExternalizer.KEY_CONNECTOR_KIND); + task = readDefaultTask(connectorKind, repositoryUrl, taskId, summary, element); + } + // attempt migration from < 3.0 task list + if (task == null) { + for (AbstractTaskListMigrator migrator : migrators) { + if (node.getNodeName().equals(migrator.getTaskElementName())) { + task = readDefaultTask(migrator.getConnectorKind(), repositoryUrl, taskId, summary, element); + taskMigrator = migrator; + break; + } + } + } + // populate common attributes + if (task != null) { + if (repositoryManager.getRepositoryConnector(task.getConnectorKind()) == null) { + errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, + "Missing connector for task with kind \"" + task.getConnectorKind() + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ + return null; + } + + readTaskInfo(task, element, parent, legacyCategory); + readAttributes(task, element); + if (taskMigrator != null) { + if (task.getSynchronizationState() == SynchronizationState.INCOMING + && task.getLastReadTimeStamp() == null) { + task.setSynchronizationState(SynchronizationState.INCOMING_NEW); + } + task.setTaskKey(task.getTaskId()); + final AbstractTaskListMigrator finalTaskMigrator = taskMigrator; + final AbstractTask finalTask = task; + SafeRunner.run(new ISafeRunnable() { + + public void handleException(Throwable e) { + errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, + "Task migration failed for task \"" + finalTask + "\"", e)); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public void run() throws Exception { + finalTaskMigrator.migrateTask(finalTask, element); + } + + }); + } + return task; + } else { + errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Missing connector for task node \"" //$NON-NLS-1$ + + node.getNodeName() + "\"")); //$NON-NLS-1$ + return null; + } + } + + private void readAttributes(IAttributeContainer container, Element parent) { + NodeList list = parent.getChildNodes(); + for (int i = 0; i < list.getLength(); i++) { + Node child = list.item(i); + if (child instanceof Element && child.getNodeName().equals(NODE_ATTRIBUTE)) { + Element element = (Element) child; + String key = element.getAttribute(KEY_KEY); + if (key.length() > 0) { + container.setAttribute(key, element.getTextContent()); + } + } + } + } + + @SuppressWarnings("deprecation") + private void readTaskInfo(AbstractTask task, Element element, ITask parent, AbstractTaskCategory legacyCategory) { + if (element.hasAttribute(KEY_CATEGORY)) { + // Migration 2.0 -> 3.0 task list. Category no longer maintained on the task element but + // task handles held within category nodes similar to query children + String categoryHandle = element.getAttribute(KEY_CATEGORY); + if (categoryHandle.equals(VAL_ROOT)) { + categoryHandle = UncategorizedTaskContainer.HANDLE; + } + //task.setCategoryHandle(categoryHandle); + parentCategoryMap.put(task, categoryHandle); + } + if (element.hasAttribute(KEY_PRIORITY)) { + task.setPriority(element.getAttribute(KEY_PRIORITY)); + } else { + task.setPriority(DEFAULT_PRIORITY); + } + if (element.hasAttribute(KEY_KIND)) { + task.setTaskKind(element.getAttribute(KEY_KIND)); + } + if (!taskActivated && element.getAttribute(KEY_ACTIVE).compareTo(VAL_TRUE) == 0) { + task.setActive(true); + taskActivated = true; + } else { + task.setActive(false); + } + if (element.hasAttribute(KEY_ISSUEURL)) { + task.setUrl(element.getAttribute(KEY_ISSUEURL)); + } else { + task.setUrl(""); //$NON-NLS-1$ + } + if (element.hasAttribute(KEY_NOTES)) { + task.setNotes(element.getAttribute(KEY_NOTES)); + } else { + task.setNotes(""); //$NON-NLS-1$ + } + if (element.hasAttribute(KEY_TIME_ESTIMATED)) { + String est = element.getAttribute(KEY_TIME_ESTIMATED); + try { + int estimate = Integer.parseInt(est); + task.setEstimatedTimeHours(estimate); + } catch (Exception e) { + task.setEstimatedTimeHours(0); + } + } else { + task.setEstimatedTimeHours(0); + } + if (element.hasAttribute(KEY_DATE_END)) { + task.setCompletionDate(getDateFromString(element.getAttribute(KEY_DATE_END))); + } else { + task.setCompletionDate(null); + } + if (element.hasAttribute(KEY_DATE_CREATION)) { + task.setCreationDate(getDateFromString(element.getAttribute(KEY_DATE_CREATION))); + } else { + task.setCreationDate(null); + } + if (element.hasAttribute(KEY_DATE_MODIFICATION)) { + task.setModificationDate(getDateFromString(element.getAttribute(KEY_DATE_MODIFICATION))); + } else { + task.setModificationDate(null); + } + if (element.hasAttribute(KEY_DATE_DUE)) { + task.setDueDate(getDateFromString(element.getAttribute(KEY_DATE_DUE))); + } else { + task.setDueDate(null); + } + // Legacy 2.3.2 -> 3.0 migration of scheduled date + boolean isFloating = false; + if (element.hasAttribute(KEY_FLOATING) && element.getAttribute(KEY_FLOATING).compareTo(VAL_TRUE) == 0) { + isFloating = true; + } else { + isFloating = false; + } + if (element.hasAttribute(KEY_DATE_REMINDER)) { + Date date = getDateFromString(element.getAttribute(KEY_DATE_REMINDER)); + if (date != null) { + if (isFloating) { + task.setScheduledForDate(TaskActivityUtil.getWeekOf(date)); + } else { + task.setScheduledForDate(TaskActivityUtil.getDayOf(date)); + } + } + } + // Scheduled date range (3.0) + if (element.hasAttribute(KEY_DATE_SCHEDULED_START) && element.hasAttribute(KEY_DATE_SCHEDULED_END)) { + Date startDate = getDateFromString(element.getAttribute(KEY_DATE_SCHEDULED_START)); + Date endDate = getDateFromString(element.getAttribute(KEY_DATE_SCHEDULED_END)); + if (startDate != null && endDate != null && startDate.compareTo(endDate) <= 0) { + Calendar calStart = TaskActivityUtil.getCalendar(); + calStart.setTime(startDate); + Calendar calEnd = TaskActivityUtil.getCalendar(); + calEnd.setTime(endDate); + if (DayDateRange.isDayRange(calStart, calEnd)) { + task.setScheduledForDate(new DayDateRange(calStart, calEnd)); + } else if (WeekDateRange.isWeekRange(calStart, calEnd)) { + task.setScheduledForDate(new WeekDateRange(calStart, calEnd)); + } else { + // Neither week nor day found, default to today + task.setScheduledForDate(TaskActivityUtil.getDayOf(new Date())); + } + } + } + if (element.hasAttribute(KEY_REMINDED) && element.getAttribute(KEY_REMINDED).compareTo(VAL_TRUE) == 0) { + task.setReminded(true); + } else { + task.setReminded(false); + } + if (element.hasAttribute(KEY_STALE) && element.getAttribute(KEY_STALE).compareTo(VAL_TRUE) == 0) { + task.setStale(true); + } else { + task.setStale(false); + } + if (element.hasAttribute(KEY_MARK_READ_PENDING) + && element.getAttribute(KEY_MARK_READ_PENDING).compareTo(VAL_TRUE) == 0) { + task.setMarkReadPending(true); + } else { + task.setMarkReadPending(false); + } + task.setSynchronizing(false); + if (element.hasAttribute(KEY_REPOSITORY_URL)) { + task.setRepositoryUrl(element.getAttribute(KEY_REPOSITORY_URL)); + } + if (element.hasAttribute(KEY_LAST_MOD_DATE) && !element.getAttribute(KEY_LAST_MOD_DATE).equals("")) { //$NON-NLS-1$ + task.setLastReadTimeStamp(element.getAttribute(KEY_LAST_MOD_DATE)); + } + if (element.hasAttribute(KEY_OWNER)) { + task.setOwner(element.getAttribute(KEY_OWNER)); + } + if (VAL_TRUE.equals(element.getAttribute(KEY_NOTIFIED_INCOMING))) { + task.setNotified(true); + } else { + task.setNotified(false); + } + if (element.hasAttribute(KEY_SYNC_STATE)) { + try { + SynchronizationState state = SynchronizationState.valueOf(element.getAttribute(KEY_SYNC_STATE)); + task.setSynchronizationState(state); + } catch (IllegalArgumentException e) { + // invalid sync state, ignore + // TODO log this to a multi-status + } + } + if (element.hasAttribute(KEY_KEY)) { + task.setTaskKey(element.getAttribute(KEY_KEY)); + } else { + task.setTaskKey(null); + } + } + + private Date getDateFromString(String dateString) { + Date date = null; + if ("".equals(dateString)) { //$NON-NLS-1$ + return null; + } + String formatString = DATE_FORMAT; + SimpleDateFormat format = new SimpleDateFormat(formatString, Locale.ENGLISH); + try { + date = format.parse(dateString); + } catch (ParseException e) { + errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Could not parse date \"" //$NON-NLS-1$ + + dateString + "\"", e)); //$NON-NLS-1$ + } + return date; + } + + private String getCategoryTagName() { + return KEY_TASK_CATEGORY; + } + + public Element createQueryElement(final RepositoryQuery query, Document doc, Element parent) { + final Element node; + if (query.getClass() == RepositoryQuery.class) { + node = doc.createElement(NODE_QUERY); + } else { + errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, + "Missing factory to externalize query \"" + query + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ + return null; + } + + node.setAttribute(KEY_HANDLE, query.getHandleIdentifier()); + node.setAttribute(KEY_CONNECTOR_KIND, query.getConnectorKind()); + node.setAttribute(KEY_NAME, query.getSummary()); + node.setAttribute(KEY_QUERY_STRING, query.getUrl()); + node.setAttribute(KEY_REPOSITORY_URL, query.getRepositoryUrl()); + if (query.getLastSynchronizedTimeStamp() != null) { + node.setAttribute(KEY_LAST_REFRESH, query.getLastSynchronizedTimeStamp()); + } + createAttributes(query, doc, node); + for (ITask hit : query.getChildren()) { + createTaskReference(KEY_QUERY_HIT, hit, doc, node); + } + + parent.appendChild(node); + return node; + } + + public Map<AbstractTask, String> getLegacyParentCategoryMap() { + return parentCategoryMap; + } + + /** + * Reads the Query from the specified Node. If taskList is not null, then also adds this query to the TaskList + * + * @throws TaskExternalizationException + */ + public RepositoryQuery readQuery(Node node) { + final Element element = (Element) node; + String repositoryUrl = element.getAttribute(DelegatingTaskExternalizer.KEY_REPOSITORY_URL); + String queryString = element.getAttribute(KEY_QUERY_STRING); + if (queryString.length() == 0) { // fall back for legacy + queryString = element.getAttribute(KEY_QUERY); + } + String label = element.getAttribute(DelegatingTaskExternalizer.KEY_NAME); + if (label.length() == 0) { // fall back for legacy + label = element.getAttribute(DelegatingTaskExternalizer.KEY_LABEL); + } + + AbstractTaskListMigrator queryMigrator = null; + RepositoryQuery query = null; + if (NODE_QUERY.equals(node.getNodeName())) { + String connectorKind = element.getAttribute(DelegatingTaskExternalizer.KEY_CONNECTOR_KIND); + query = readDefaultQuery(connectorKind, repositoryUrl, queryString, label, element); + } + // attempt migration from < 3.0 task list + if (query == null) { + for (AbstractTaskListMigrator migrator : migrators) { + Set<String> queryTagNames = migrator.getQueryElementNames(); + if (queryTagNames != null && queryTagNames.contains(node.getNodeName())) { + query = readDefaultQuery(migrator.getConnectorKind(), repositoryUrl, queryString, label, element); + queryMigrator = migrator; + break; + } + } + } + // populate common attributes + if (query != null) { + if (repositoryManager.getRepositoryConnector(query.getConnectorKind()) == null) { + errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, + "Missing connector for query with kind \"" + query.getConnectorKind() + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ + return null; + } + + if (element.getAttribute(DelegatingTaskExternalizer.KEY_LAST_REFRESH) != null + && !element.getAttribute(DelegatingTaskExternalizer.KEY_LAST_REFRESH).equals("")) { //$NON-NLS-1$ + query.setLastSynchronizedStamp(element.getAttribute(DelegatingTaskExternalizer.KEY_LAST_REFRESH)); + } + String handle = element.getAttribute(DelegatingTaskExternalizer.KEY_HANDLE); + if (handle.length() > 0) { + query.setHandleIdentifier(handle); + } + readAttributes(query, element); + if (queryMigrator != null) { + query.setHandleIdentifier(label); + final AbstractTaskListMigrator finalQueryMigrator = queryMigrator; + final RepositoryQuery finalQuery = query; + SafeRunner.run(new ISafeRunnable() { + + public void handleException(Throwable e) { + errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, + "Query migration failed for query \"" + finalQuery + "\"", e)); //$NON-NLS-1$ //$NON-NLS-2$ + } + + public void run() throws Exception { + finalQueryMigrator.migrateQuery(finalQuery, element); + } + + }); + } + return query; + } else { + errors.add(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, "Missing connector for query node \"" //$NON-NLS-1$ + + node.getNodeName() + "\"")); //$NON-NLS-1$ + return null; + } + } + + private RepositoryQuery readDefaultQuery(String connectorKind, String repositoryUrl, String queryString, + String label, Element childElement) { + TaskRepository taskRepository = repositoryModel.getTaskRepository(connectorKind, repositoryUrl); + IRepositoryQuery query = repositoryModel.createRepositoryQuery(taskRepository); + query.setSummary(label); + query.setUrl(queryString); + return (RepositoryQuery) query; + } + + private AbstractTask readDefaultTask(String connectorKind, String repositoryUrl, String taskId, String summary, + Element element) { + TaskRepository taskRepository = repositoryModel.getTaskRepository(connectorKind, repositoryUrl); + if (repositoryUrl.equals(LocalRepositoryConnector.REPOSITORY_URL)) { + LocalTask task = new LocalTask(taskId, summary); + return task; + } + ITask task = repositoryModel.createTask(taskRepository, taskId); + task.setSummary(summary); + return (AbstractTask) task; + } + + public void reset() { + parentCategoryMap.clear(); + errors.clear(); + } + + public void clearErrorStatus() { + errors.clear(); + } + + public Status getErrorStatus() { + if (errors.size() > 0) { + return new MultiStatus(ITasksCoreConstants.ID_PLUGIN, 0, errors.toArray(new IStatus[0]), + "Problems encounted while externalizing task list", null); //$NON-NLS-1$ + } + return null; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/ExternalizationManager.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/ExternalizationManager.java new file mode 100644 index 000000000..b9a94a7ff --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/ExternalizationManager.java @@ -0,0 +1,244 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.externalization; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.ISafeRunnable; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.mylyn.commons.core.CoreUtil; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.commons.net.Policy; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.internal.tasks.core.externalization.IExternalizationContext.Kind; + +/** + * @author Rob Elves + * @since 3.0 + */ +public class ExternalizationManager { + + private static final int SAVE_DELAY = 90 * 1000; + + private final ExternalizationJob saveJob; + + private IStatus loadStatus; + + private String rootFolderPath; + + private static volatile boolean saveDisabled = false; + + private final List<IExternalizationParticipant> externalizationParticipants; + + private boolean forceSave = false; + + public ExternalizationManager(String rootFolderPath) { + Assert.isNotNull(rootFolderPath); + this.externalizationParticipants = new CopyOnWriteArrayList<IExternalizationParticipant>(); + this.forceSave = false; + this.saveJob = createJob(); + setRootFolderPath(rootFolderPath); + } + + private ExternalizationJob createJob() { + ExternalizationJob job = new ExternalizationJob(Messages.ExternalizationManager_Task_List_Save_Job); + job.setUser(false); + job.setSystem(true); + return job; + } + + public void addParticipant(IExternalizationParticipant participant) { + Assert.isNotNull(participant); + externalizationParticipants.add(participant); + } + + public IStatus load() { + try { + saveDisabled = true; + loadStatus = null; + + List<IStatus> statusList = new ArrayList<IStatus>(); + IProgressMonitor monitor = Policy.monitorFor(null); + for (IExternalizationParticipant participant : externalizationParticipants) { + IStatus status = load(participant, monitor); + if (status != null) { + statusList.add(status); + } + } + + if (statusList.size() > 0) { + loadStatus = new MultiStatus(ITasksCoreConstants.ID_PLUGIN, IStatus.ERROR, + statusList.toArray(new IStatus[0]), "Failed to load Task List", null); //$NON-NLS-1$ + } + return loadStatus; + } finally { + saveDisabled = false; + } + } + + public IStatus load(final IExternalizationParticipant participant, final IProgressMonitor monitor) { + final IStatus[] result = new IStatus[1]; + final ExternalizationContext context = new ExternalizationContext(Kind.LOAD, rootFolderPath); + ISchedulingRule rule = participant.getSchedulingRule(); + try { + Job.getJobManager().beginRule(rule, monitor); + SafeRunner.run(new ISafeRunnable() { + public void handleException(Throwable e) { + if (e instanceof CoreException) { + result[0] = ((CoreException) e).getStatus(); + } else { + result[0] = new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Load participant failed", //$NON-NLS-1$ + e); + } + } + + public void run() throws Exception { + participant.execute(context, monitor); + } + }); + } finally { + Job.getJobManager().endRule(rule); + } + return result[0]; + } + + public void setRootFolderPath(String rootFolderPath) { + Assert.isNotNull(rootFolderPath); + this.rootFolderPath = rootFolderPath; + saveJob.setContext(new ExternalizationContext(Kind.SAVE, rootFolderPath)); + } + + public void requestSave() { + if (!saveDisabled) { + if (!CoreUtil.TEST_MODE) { + saveJob.schedule(SAVE_DELAY); + } else { + saveJob.run(new NullProgressMonitor()); + } + } + } + + public void stop() { + try { + saveDisabled = true; + + // run save job as early as possible + saveJob.wakeUp(); + saveJob.join(); + } catch (InterruptedException e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Task List save on shutdown canceled.", e)); //$NON-NLS-1$ + } + } + + /** + * Clients invoking this method must hold all necessary scheduling rules. + */ + public void save(boolean force) { + try { + forceSave = force; + saveJob.run(new NullProgressMonitor()); + } finally { + forceSave = false; + } + } + + public IStatus getLoadStatus() { + return loadStatus; + } + + private class ExternalizationJob extends Job { + + private volatile IExternalizationContext context; + + public ExternalizationJob(String jobTitle) { + super(jobTitle); + } + + public IExternalizationContext getContext() { + return context; + } + + public void setContext(IExternalizationContext saveContext) { + this.context = saveContext; + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + IExternalizationContext context = this.context; + switch (context.getKind()) { + case SAVE: + try { + monitor.beginTask(Messages.ExternalizationManager_Saving_, externalizationParticipants.size()); + for (IExternalizationParticipant participant : externalizationParticipants) { + ISchedulingRule rule = participant.getSchedulingRule(); + if (forceSave || participant.isDirty()) { + try { + Job.getJobManager().beginRule(rule, monitor); + monitor.setTaskName(MessageFormat.format(Messages.ExternalizationManager_Saving_X, + participant.getDescription())); + participant.execute(context, new SubProgressMonitor(monitor, IProgressMonitor.UNKNOWN)); + } catch (CoreException e) { + StatusHandler.log(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, + "Save failed for " + participant.getDescription(), e)); //$NON-NLS-1$ + } finally { + Job.getJobManager().endRule(rule); + } + } + monitor.worked(1); + } + } finally { + monitor.done(); + } + break; + default: + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Unsupported externalization kind: " + context.getKind())); //$NON-NLS-1$ + } + return Status.OK_STATUS; + } + } + + private class ExternalizationContext implements IExternalizationContext { + + private final Kind kind; + + private final String rootPath; + + public ExternalizationContext(IExternalizationContext.Kind kind, String rootPath) { + this.kind = kind; + this.rootPath = rootPath; + } + + public Kind getKind() { + return kind; + } + + public String getRootPath() { + return rootPath; + } + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/IExternalizationContext.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/IExternalizationContext.java new file mode 100644 index 000000000..3e1163880 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/IExternalizationContext.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.externalization; + +/** + * @author Rob Elves + */ +public interface IExternalizationContext { + + public enum Kind { + SAVE, SNAPSHOT, LOAD; + } + + public abstract Kind getKind(); + + public abstract String getRootPath(); +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/IExternalizationParticipant.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/IExternalizationParticipant.java new file mode 100644 index 000000000..a55cbe289 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/IExternalizationParticipant.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.externalization; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.jobs.ISchedulingRule; + +/** + * @author Rob Elves + */ +public interface IExternalizationParticipant { + + public abstract boolean isDirty(); + + public abstract ISchedulingRule getSchedulingRule(); + + public abstract void execute(IExternalizationContext context, IProgressMonitor monitor) throws CoreException; + + public abstract String getDescription(); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/Messages.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/Messages.java new file mode 100644 index 000000000..792f462b1 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/Messages.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.externalization; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.mylyn.internal.tasks.core.externalization.messages"; //$NON-NLS-1$ + + static { + // load message values from bundle file + reloadMessages(); + } + + public static void reloadMessages() { + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + public static String ExternalizationManager_Saving_X; + + public static String ExternalizationManager_Saving_; + + public static String ExternalizationManager_Task_List_Save_Job; + + public static String TaskListExternalizationParticipant_Task_List; +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/TaskListExternalizationParticipant.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/TaskListExternalizationParticipant.java new file mode 100644 index 000000000..2c90dadcc --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/TaskListExternalizationParticipant.java @@ -0,0 +1,183 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.externalization; + +import java.io.File; +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.jobs.ISchedulingRule; +import org.eclipse.mylyn.internal.tasks.core.ITaskListChangeListener; +import org.eclipse.mylyn.internal.tasks.core.ITaskListRunnable; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.internal.tasks.core.LocalRepositoryConnector; +import org.eclipse.mylyn.internal.tasks.core.RepositoryModel; +import org.eclipse.mylyn.internal.tasks.core.TaskContainerDelta; +import org.eclipse.mylyn.internal.tasks.core.TaskList; +import org.eclipse.mylyn.internal.tasks.core.TaskRepositoryManager; +import org.eclipse.mylyn.internal.tasks.core.UnmatchedTaskContainer; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITaskActivationListener; +import org.eclipse.mylyn.tasks.core.TaskRepository; + +/** + * @author Rob Elves + */ +public class TaskListExternalizationParticipant extends AbstractExternalizationParticipant implements + IExternalizationParticipant, ITaskListChangeListener, ITaskActivationListener { + + private static final String DESCRIPTION = Messages.TaskListExternalizationParticipant_Task_List; + + private final ExternalizationManager manager; + + private final TaskListExternalizer taskListWriter; + + private final TaskList taskList; + + private boolean dirty; + + private final TaskRepositoryManager taskRepositoryManager; + + private final RepositoryModel repositoryModel; + + public TaskListExternalizationParticipant(RepositoryModel repositoryModel, TaskList taskList, + TaskListExternalizer taskListExternalizer, ExternalizationManager manager, + TaskRepositoryManager repositoryManager) { + this.repositoryModel = repositoryModel; + this.manager = manager; + this.taskList = taskList; + this.taskListWriter = taskListExternalizer; + this.taskRepositoryManager = repositoryManager; + } + + @Override + public ISchedulingRule getSchedulingRule() { + return TaskList.getSchedulingRule(); + } + + @Override + public boolean isDirty() { + return dirty; + } + + @Override + public void load(final File sourceFile, IProgressMonitor monitor) throws CoreException { + ITaskListRunnable loadRunnable = new ITaskListRunnable() { + public void execute(IProgressMonitor monitor) throws CoreException { + resetTaskList(); + taskListWriter.readTaskList(taskList, sourceFile); + } + }; + + taskList.run(loadRunnable, monitor); + } + + @Override + protected boolean performLoad(File dataFile, IProgressMonitor monitor) throws CoreException { + if (super.performLoad(dataFile, monitor)) { + return true; + } else { + try { + // attempt restore of old Mylyn tasklist.xml.zip + File oldTasklist = new File(dataFile.getParent(), ITasksCoreConstants.OLD_M_2_TASKLIST_FILENAME); + if (oldTasklist.exists()) { + load(oldTasklist, monitor); + return true; + } + } catch (CoreException e) { + // ignore + } + } + return false; + } + + /** + * public for tests + */ + public void resetTaskList() { + repositoryModel.clear(); + taskList.reset(); + prepareOrphanContainers(); + } + + private void prepareOrphanContainers() { + for (TaskRepository repository : taskRepositoryManager.getAllRepositories()) { + if (!repository.getConnectorKind().equals(LocalRepositoryConnector.CONNECTOR_KIND)) { + taskList.addUnmatchedContainer(new UnmatchedTaskContainer(repository.getConnectorKind(), + repository.getRepositoryUrl())); + } + } + } + + @Override + public void save(final File targetFile, IProgressMonitor monitor) throws CoreException { + ITaskListRunnable saveRunnable = new ITaskListRunnable() { + public void execute(IProgressMonitor monitor) throws CoreException { + synchronized (TaskListExternalizationParticipant.this) { + dirty = false; + } + taskListWriter.writeTaskList(taskList, targetFile); + } + }; + + taskList.run(saveRunnable, monitor); + } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public String getFileName() { + return ITasksCoreConstants.DEFAULT_TASK_LIST_FILE; + } + + public void containersChanged(Set<TaskContainerDelta> containers) { + for (TaskContainerDelta taskContainerDelta : containers) { + if (!taskContainerDelta.isTransient()) { + synchronized (TaskListExternalizationParticipant.this) { + dirty = true; + } + manager.requestSave(); + return; + } + } + } + + public void preTaskActivated(ITask task) { + // ignore + + } + + public void preTaskDeactivated(ITask task) { + // ignore + + } + + public void taskActivated(ITask task) { + synchronized (TaskListExternalizationParticipant.this) { + dirty = true; + } + manager.requestSave(); + return; + } + + public void taskDeactivated(ITask task) { + synchronized (TaskListExternalizationParticipant.this) { + dirty = true; + } + manager.requestSave(); + return; + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/TaskListExternalizer.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/TaskListExternalizer.java new file mode 100644 index 000000000..8f7262594 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/TaskListExternalizer.java @@ -0,0 +1,320 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + * Ken Sueda - improvements + * Jevgeni Holodkov - improvements + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.externalization; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.internal.tasks.core.AbstractTask; +import org.eclipse.mylyn.internal.tasks.core.AbstractTaskCategory; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.internal.tasks.core.ITransferList; +import org.eclipse.mylyn.internal.tasks.core.RepositoryModel; +import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery; +import org.eclipse.mylyn.tasks.core.AbstractTaskListMigrator; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * @author Mik Kersten + * @author Rob Elves + */ +public class TaskListExternalizer { + + private static final String ERROR_TASKLIST_READ = "Failed to load Task List"; //$NON-NLS-1$ + + private static final String TRANSFORM_PROPERTY_VERSION = "version"; //$NON-NLS-1$ + + // May 2007: There was a bug when reading in 1.1 + // Result was an infinite loop within the parser + private static final String XML_VERSION = "1.0"; //$NON-NLS-1$ + + public static final String ATTRIBUTE_VERSION = "Version"; //$NON-NLS-1$ + + public static final String ELEMENT_TASK_LIST = "TaskList"; //$NON-NLS-1$ + + // Mylyn 3.0 + private static final String VALUE_VERSION = "2.0"; //$NON-NLS-1$ + + // Mylyn 2.3.2 + //private static final String VALUE_VERSION_1_0_1 = "1.0.1"; + + private static final String VALUE_VERSION_1_0_0 = "1.0.0"; //$NON-NLS-1$ + + private final DelegatingTaskExternalizer delegatingExternalizer; + + private final List<Node> orphanedNodes = new ArrayList<Node>(); + + private String readVersion = ""; //$NON-NLS-1$ + + public TaskListExternalizer(RepositoryModel repositoryModel, IRepositoryManager repositoryManager) { + this.delegatingExternalizer = new DelegatingTaskExternalizer(repositoryModel, repositoryManager); + } + + public void initialize(List<AbstractTaskListMigrator> migrators) { + this.delegatingExternalizer.initialize(migrators); + } + + public void writeTaskList(ITransferList taskList, File outFile) throws CoreException { + try { + FileOutputStream outStream = new FileOutputStream(outFile); + try { + Document doc = createTaskListDocument(taskList); + + ZipOutputStream zipOutStream = new ZipOutputStream(outStream); + + ZipEntry zipEntry = new ZipEntry(ITasksCoreConstants.OLD_TASK_LIST_FILE); + zipOutStream.putNextEntry(zipEntry); + zipOutStream.setMethod(ZipOutputStream.DEFLATED); + + writeDocument(doc, zipOutStream); + + zipOutStream.flush(); + zipOutStream.closeEntry(); + zipOutStream.finish(); + } finally { + outStream.close(); + } + } catch (IOException e) { + throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Saving Task List failed", //$NON-NLS-1$ + e)); + } + } + + private Document createTaskListDocument(ITransferList taskList) throws CoreException { + Document doc = createDocument(); + + delegatingExternalizer.clearErrorStatus(); + + Element root = doc.createElement(ELEMENT_TASK_LIST); + root.setAttribute(ATTRIBUTE_VERSION, VALUE_VERSION); + doc.appendChild(root); + + // create task nodes... + for (AbstractTask task : taskList.getAllTasks()) { + delegatingExternalizer.createTaskElement(task, doc, root); + } + + // create the category nodes... + for (AbstractTaskCategory category : taskList.getCategories()) { + delegatingExternalizer.createCategoryElement(category, doc, root); + } + + // create query nodes... + for (RepositoryQuery query : taskList.getQueries()) { + delegatingExternalizer.createQueryElement(query, doc, root); + } + + // Persist orphaned tasks... + for (Node node : orphanedNodes) { + Node tempNode = doc.importNode(node, true); + if (tempNode != null) { + root.appendChild(tempNode); + } + } + + if (delegatingExternalizer.getErrorStatus() != null) { + StatusHandler.log(delegatingExternalizer.getErrorStatus()); + } + + return doc; + } + + private void writeDocument(Document doc, OutputStream outputStream) throws CoreException { + Source source = new DOMSource(doc); + Result result = new StreamResult(outputStream); + try { + Transformer xformer = TransformerFactory.newInstance().newTransformer(); + xformer.setOutputProperty(TRANSFORM_PROPERTY_VERSION, XML_VERSION); + xformer.transform(source, result); + } catch (TransformerException e) { + throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Failed write task list", //$NON-NLS-1$ + e)); + } + } + + private Document createDocument() throws CoreException { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db; + try { + db = dbf.newDocumentBuilder(); + return db.newDocument(); + } catch (ParserConfigurationException e) { + throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Failed to create document", e)); //$NON-NLS-1$ + } + } + + public void readTaskList(ITransferList taskList, File inFile) throws CoreException { + if (!inFile.exists()) { + throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Task list file not found \"" + inFile.getAbsolutePath() + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (inFile.length() == 0) { + throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Task list file contains no data \"" + inFile.getAbsolutePath() + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ + } + + delegatingExternalizer.reset(); + orphanedNodes.clear(); + + Document doc = openTaskList(inFile); + Element root = doc.getDocumentElement(); + readVersion = root.getAttribute(ATTRIBUTE_VERSION); + if (readVersion.equals(VALUE_VERSION_1_0_0)) { + throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Task list version \"" //$NON-NLS-1$ + + readVersion + "\" not supported")); //$NON-NLS-1$ + } + + NodeList list = root.getChildNodes(); + + // read tasks + Map<AbstractTask, NodeList> tasksWithSubtasks = new HashMap<AbstractTask, NodeList>(); + for (int i = 0; i < list.getLength(); i++) { + Node child = list.item(i); + if (!child.getNodeName().endsWith(DelegatingTaskExternalizer.KEY_CATEGORY) + && !child.getNodeName().endsWith(DelegatingTaskExternalizer.KEY_QUERY)) { + AbstractTask task = delegatingExternalizer.readTask(child, null, null); + if (task != null) { + taskList.addTask(task); + if (child.getChildNodes() != null && child.getChildNodes().getLength() > 0) { + tasksWithSubtasks.put(task, child.getChildNodes()); + } + } else { + orphanedNodes.add(child); + } + } + } + // create subtask hierarchy + for (AbstractTask task : tasksWithSubtasks.keySet()) { + NodeList nodes = tasksWithSubtasks.get(task); + delegatingExternalizer.readTaskReferences(task, nodes, taskList); + } + + // read queries + for (int i = 0; i < list.getLength(); i++) { + Node child = list.item(i); + if (child.getNodeName().endsWith(DelegatingTaskExternalizer.KEY_QUERY)) { + RepositoryQuery query = delegatingExternalizer.readQuery(child); + if (query != null) { + taskList.addQuery(query); + if (child.getChildNodes() != null && child.getChildNodes().getLength() > 0) { + delegatingExternalizer.readTaskReferences(query, child.getChildNodes(), taskList); + } + } else { + orphanedNodes.add(child); + } + } + } + + // Read Categories + for (int i = 0; i < list.getLength(); i++) { + Node child = list.item(i); + if (child.getNodeName().endsWith(DelegatingTaskExternalizer.KEY_CATEGORY)) { + delegatingExternalizer.readCategory(child, taskList); + } + } + + // Legacy migration for task nodes that have the old Category handle on the element + Map<AbstractTask, String> legacyParentCategoryMap = delegatingExternalizer.getLegacyParentCategoryMap(); + if (legacyParentCategoryMap.size() > 0) { + for (AbstractTask task : legacyParentCategoryMap.keySet()) { + AbstractTaskCategory category = taskList.getContainerForHandle(legacyParentCategoryMap.get(task)); + if (category != null) { + taskList.addTask(task, category); + } + } + } + +// if (delegatingExternalizer.getErrorStatus() != null) { +// StatusHandler.log(delegatingExternalizer.getErrorStatus()); +// } + } + + /** + * Opens the specified XML file and parses it into a DOM Document. + * + * Filename - the name of the file to open Return - the Document built from the XML file Throws - XMLException if + * the file cannot be parsed as XML - IOException if the file cannot be opened + * + * @throws CoreException + * + */ + private Document openTaskList(File inputFile) throws CoreException { + InputStream in = null; + try { + if (inputFile.getName().endsWith(ITasksCoreConstants.FILE_EXTENSION)) { + in = new ZipInputStream(new FileInputStream(inputFile)); + ZipEntry entry = ((ZipInputStream) in).getNextEntry(); + while (entry != null) { + if (ITasksCoreConstants.OLD_TASK_LIST_FILE.equals(entry.getName())) { + break; + } + entry = ((ZipInputStream) in).getNextEntry(); + } + if (entry == null) { + throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Task list file contains no entry for the task list")); //$NON-NLS-1$ + } + } else { + in = new FileInputStream(inputFile); + } + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + return builder.parse(in); + } catch (Exception e) { + throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, ERROR_TASKLIST_READ, e)); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Failed to close task list", e)); //$NON-NLS-1$ + } + } + } + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/messages.properties b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/messages.properties new file mode 100644 index 000000000..399cfc7fc --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/messages.properties @@ -0,0 +1,5 @@ +ExternalizationManager_Saving_X=Saving {0} +ExternalizationManager_Saving_=Saving... +ExternalizationManager_Task_List_Save_Job=Task List Save Job + +TaskListExternalizationParticipant_Task_List=Task List diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/messages.properties b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/messages.properties new file mode 100644 index 000000000..59e5baf5c --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/messages.properties @@ -0,0 +1,26 @@ +DayDateRange___Today=\ - Today + +LocalRepositoryConnector_Local=Local +LocalRepositoryConnector_Local_Task_Repository=Local Task Repository +LocalRepositoryConnector_New_Task=New Task + +RepositoryExternalizationParticipant_Task_Repositories=Task Repositories + +TaskRepositoryManager_No_repository_available=No repository available, please add one using the Task Repositories view. + +UncategorizedTaskContainer_Uncategorized=Uncategorized + +UnmatchedTaskContainer_Unmatched=Unmatched + +UnsubmittedTaskContainer_Unsubmitted=Unsubmitted + +WeekDateRange_Next_Week=Next Week +WeekDateRange_Previous_Week=Previous Week +WeekDateRange_This_Week=This Week +WeekDateRange_Two_Weeks=Two Weeks + +PriorityLevel_High=High +PriorityLevel_Low=Low +PriorityLevel_Normal=Normal +PriorityLevel_Very_High=Very High +PriorityLevel_Very_Low=Very Low diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/Messages.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/Messages.java new file mode 100644 index 000000000..d9ae21cab --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/Messages.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.sync; + +import org.eclipse.osgi.util.NLS; + +public class Messages extends NLS { + private static final String BUNDLE_NAME = "org.eclipse.mylyn.internal.tasks.core.sync.messages"; //$NON-NLS-1$ + + static { + // load message values from bundle file + reloadMessages(); + } + + public static void reloadMessages() { + NLS.initializeMessages(BUNDLE_NAME, Messages.class); + } + + public static String SubmitTaskAttachmentJob_Sending_data; + + public static String SubmitTaskAttachmentJob_Submitting_attachment; + + public static String SubmitTaskAttachmentJob_Updating_task; + + public static String SubmitTaskJob_Receiving_data; + + public static String SubmitTaskJob_Sending_data; + + public static String SubmitTaskJob_Submitting_task; + + public static String SynchronizeQueriesJob_Max_allowed_number_of_hits_returned_exceeded; + + public static String SynchronizeQueriesJob_Processing; + + public static String SynchronizeQueriesJob_Querying_repository; + + public static String SynchronizeQueriesJob_Receiving_related_tasks; + + public static String SynchronizeQueriesJob_Synchronizing_Queries; + + public static String SynchronizeQueriesJob_Synchronizing_query_X; + + public static String SynchronizeQueriesJob_Updating_repository_state; + + public static String SynchronizeRepositoriesJob_Processing; + + public static String SynchronizeRepositoriesJob_Processing_; + + public static String SynchronizeRepositoriesJob_Synchronizing_Task_List; + + public static String SynchronizeRepositoriesJob_Updating_repository_configuration_for_X; + + public static String SynchronizeTasksJob_Processing; + + public static String SynchronizeTasksJob_Receiving_task_X; + + public static String SynchronizeTasksJob_Synchronizing_Tasks__X_; + + public static String SynchronizeTasksJob_Receiving_X_tasks_from_X; +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SubmitTaskAttachmentJob.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SubmitTaskAttachmentJob.java new file mode 100644 index 000000000..f9f08a021 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SubmitTaskAttachmentJob.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.sync; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.commons.net.Policy; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants.MutexSchedulingRule; +import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManager; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.RepositoryResponse; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskAttachmentHandler; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskAttachmentSource; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tasks.core.sync.SubmitJob; + +/** + * @author Steffen Pingel + */ +public class SubmitTaskAttachmentJob extends SubmitJob { + + private final TaskAttribute attachmentAttribute; + + private final String comment; + + private final AbstractRepositoryConnector connector; + + private IStatus error; + + private final AbstractTaskAttachmentSource source; + + private final ITask task; + + private final TaskRepository taskRepository; + + private final TaskDataManager taskDataManager; + + public SubmitTaskAttachmentJob(TaskDataManager taskDataManager, AbstractRepositoryConnector connector, + TaskRepository taskRepository, ITask task, AbstractTaskAttachmentSource source, String comment, + TaskAttribute attachmentAttribute) { + super("Submitting Attachment"); //$NON-NLS-1$ + this.taskDataManager = taskDataManager; + this.connector = connector; + this.taskRepository = taskRepository; + this.task = task; + this.source = source; + this.comment = comment; + this.attachmentAttribute = attachmentAttribute; + setRule(new MutexSchedulingRule()); + } + + @Override + public RepositoryResponse getResponse() { + return null; + } + + @Override + public IStatus getStatus() { + return error; + } + + @Override + public ITask getTask() { + return task; + } + + @Override + public IStatus run(IProgressMonitor monitor) { + final AbstractTaskAttachmentHandler attachmentHandler = connector.getTaskAttachmentHandler(); + if (attachmentHandler == null) { + error = new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "The task repository does not support attachments."); //$NON-NLS-1$ + return Status.OK_STATUS; + } + try { + monitor.beginTask(Messages.SubmitTaskAttachmentJob_Submitting_attachment, + 2 * (1 + getSubmitJobListeners().length) * 100); + monitor.subTask(Messages.SubmitTaskAttachmentJob_Sending_data); + attachmentHandler.postContent(taskRepository, task, source, comment, attachmentAttribute, + Policy.subMonitorFor(monitor, 100)); + fireTaskSubmitted(monitor); + monitor.subTask(Messages.SubmitTaskAttachmentJob_Updating_task); + TaskData taskData = connector.getTaskData(taskRepository, task.getTaskId(), Policy.subMonitorFor(monitor, + 100)); + taskDataManager.putUpdatedTaskData(task, taskData, true); + fireTaskSynchronized(monitor); + } catch (CoreException e) { + error = e.getStatus(); + } catch (OperationCanceledException e) { + return Status.CANCEL_STATUS; + } finally { + monitor.done(); + } + fireDone(); + return Status.OK_STATUS; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SubmitTaskJob.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SubmitTaskJob.java new file mode 100644 index 000000000..d41887104 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SubmitTaskJob.java @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.sync; + +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.commons.net.Policy; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.internal.tasks.core.TaskTask; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants.MutexSchedulingRule; +import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManager; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.RepositoryResponse; +import org.eclipse.mylyn.tasks.core.RepositoryStatus; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tasks.core.sync.SubmitJob; + +/** + * @author Steffen Pingel + */ +public class SubmitTaskJob extends SubmitJob { + + private final TaskRepository taskRepository; + + private final TaskData taskData; + + private final AbstractRepositoryConnector connector; + + private IStatus errorStatus; + + private ITask task; + + private final Set<TaskAttribute> oldAttributes; + + private final TaskDataManager taskDataManager; + + private RepositoryResponse response; + + public SubmitTaskJob(TaskDataManager taskDataManager, AbstractRepositoryConnector connector, + TaskRepository taskRepository, ITask task, TaskData taskData, Set<TaskAttribute> oldAttributes) { + super("Submitting Task"); //$NON-NLS-1$ + this.taskDataManager = taskDataManager; + this.connector = connector; + this.taskRepository = taskRepository; + this.task = task; + this.taskData = taskData; + this.oldAttributes = oldAttributes; + setRule(new MutexSchedulingRule()); + } + + @Override + protected IStatus run(IProgressMonitor jobMonitor) { + monitor.attach(jobMonitor); + try { + monitor.beginTask(Messages.SubmitTaskJob_Submitting_task, 2 * (1 + getSubmitJobListeners().length) * 100); + + // post task data + AbstractTaskDataHandler taskDataHandler = connector.getTaskDataHandler(); + monitor.subTask(Messages.SubmitTaskJob_Sending_data); + response = taskDataHandler.postTaskData(taskRepository, taskData, oldAttributes, Policy.subMonitorFor( + monitor, 100)); + if (response == null || response.getTaskId() == null) { + throw new CoreException(new RepositoryStatus(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + RepositoryStatus.ERROR_INTERNAL, + "Task could not be created. No additional information was provided by the connector.")); //$NON-NLS-1$ + } + fireTaskSubmitted(monitor); + + // update task in task list + String taskId = response.getTaskId(); + monitor.subTask(Messages.SubmitTaskJob_Receiving_data); + TaskData updatedTaskData = connector.getTaskData(taskRepository, taskId, Policy.subMonitorFor(monitor, 100)); + task = createTask(monitor, updatedTaskData); + taskDataManager.putSubmittedTaskData(task, updatedTaskData); + fireTaskSynchronized(monitor); + } catch (CoreException e) { + errorStatus = e.getStatus(); + } catch (OperationCanceledException e) { + return Status.CANCEL_STATUS; + } catch (Exception e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Unexpected error during task submission", e)); //$NON-NLS-1$ + errorStatus = new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Unexpected error: " //$NON-NLS-1$ + + e.getMessage(), e); + } finally { + monitor.done(); + } + fireDone(); + return Status.OK_STATUS; + } + + private ITask createTask(IProgressMonitor monitor, TaskData updatedTaskData) throws CoreException { + if (taskData.isNew()) { + task = new TaskTask(connector.getConnectorKind(), taskRepository.getRepositoryUrl(), + updatedTaskData.getTaskId()); + } + return task; + } + + @Override + public RepositoryResponse getResponse() { + return response; + } + + @Override + public IStatus getStatus() { + return errorStatus; + } + + @Override + public ITask getTask() { + return task; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizationSession.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizationSession.java new file mode 100644 index 000000000..feeb2164b --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizationSession.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.sync; + +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManager; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tasks.core.sync.ISynchronizationSession; + +/** + * @author Steffen Pingel + * @since 3.0 + */ +public class SynchronizationSession implements ISynchronizationSession { + + private Set<ITask> changedTasks; + + private Object data; + + private boolean fullSynchronization; + + private boolean performQueries; + + private Set<ITask> staleTasks; + + private IStatus status; + + private TaskDataManager taskDataManager; + + private TaskRepository taskRepository; + + private Set<ITask> tasks; + + private boolean user; + + public SynchronizationSession() { + } + + public SynchronizationSession(TaskDataManager taskDataManager) { + this.taskDataManager = taskDataManager; + } + + public Set<ITask> getChangedTasks() { + return changedTasks; + } + + public Object getData() { + return data; + } + + public Set<ITask> getStaleTasks() { + return staleTasks; + } + + public IStatus getStatus() { + return status; + } + + public TaskDataManager getTaskDataManager() { + return taskDataManager; + } + + public TaskRepository getTaskRepository() { + return taskRepository; + } + + public Set<ITask> getTasks() { + return tasks; + } + + public boolean isFullSynchronization() { + return fullSynchronization; + } + + public boolean isUser() { + return user; + } + + public void markStale(ITask task) { + if (staleTasks == null) { + staleTasks = new HashSet<ITask>(); + } + staleTasks.add(task); + } + + public boolean needsPerformQueries() { + return performQueries; + } + + public void putTaskData(ITask task, TaskData taskData) throws CoreException { + if (taskDataManager != null) { + taskDataManager.putUpdatedTaskData(task, taskData, false); + } + } + + public void setChangedTasks(Set<ITask> changedTasks) { + this.changedTasks = changedTasks; + } + + public void setData(Object data) { + this.data = data; + } + + public void setFullSynchronization(boolean fullSynchronization) { + this.fullSynchronization = fullSynchronization; + } + + public void setNeedsPerformQueries(boolean performQueries) { + this.performQueries = performQueries; + } + + public void setStatus(IStatus status) { + this.status = status; + } + + public void setTaskRepository(TaskRepository taskRepository) { + this.taskRepository = taskRepository; + } + + public void setTasks(Set<ITask> tasks) { + this.tasks = tasks; + } + + public void setUser(boolean user) { + this.user = user; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeQueriesJob.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeQueriesJob.java new file mode 100644 index 000000000..38baaf427 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeQueriesJob.java @@ -0,0 +1,322 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.sync; + +import java.text.MessageFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.commons.net.Policy; +import org.eclipse.mylyn.internal.tasks.core.AbstractTask; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery; +import org.eclipse.mylyn.internal.tasks.core.TaskList; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants.ObjectSchedulingRule; +import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManager; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.IRepositoryModel; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.ITask.SynchronizationState; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tasks.core.data.TaskDataCollector; +import org.eclipse.mylyn.tasks.core.data.TaskRelation; +import org.eclipse.mylyn.tasks.core.sync.ISynchronizationSession; +import org.eclipse.mylyn.tasks.core.sync.SynchronizationJob; + +/** + * @author Mik Kersten + * @author Rob Elves + * @author Steffen Pingel + */ +public class SynchronizeQueriesJob extends SynchronizationJob { + + private class TaskCollector extends TaskDataCollector { + + private final Set<ITask> removedQueryResults; + + private final RepositoryQuery repositoryQuery; + + private int resultCount; + + private final SynchronizationSession session; + + public TaskCollector(RepositoryQuery repositoryQuery, SynchronizationSession session) { + this.repositoryQuery = repositoryQuery; + this.session = session; + this.removedQueryResults = new HashSet<ITask>(repositoryQuery.getChildren()); + } + + @Override + public void accept(TaskData taskData) { + ITask task = taskList.getTask(taskData.getRepositoryUrl(), taskData.getTaskId()); + if (task == null) { + task = tasksModel.createTask(repository, taskData.getTaskId()); + ((AbstractTask) task).setSynchronizationState(SynchronizationState.INCOMING_NEW); + if (taskData.isPartial() && connector.canSynchronizeTask(repository, task)) { + session.markStale(task); + } + } else { + removedQueryResults.remove(task); + } + try { + session.putTaskData(task, taskData); + } catch (CoreException e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Failed to save task", e)); //$NON-NLS-1$ + } + taskList.addTask(task, repositoryQuery); + resultCount++; + } + + public Set<ITask> getRemovedChildren() { + return removedQueryResults; + } + + public int getResultCount() { + return resultCount; + } + + } + + public static final String MAX_HITS_REACHED = Messages.SynchronizeQueriesJob_Max_allowed_number_of_hits_returned_exceeded; + + private final AbstractRepositoryConnector connector; + + private final Set<RepositoryQuery> queries; + + private final TaskRepository repository; + + private final TaskDataManager taskDataManager; + + private final TaskList taskList; + + private final IRepositoryModel tasksModel; + + private final List<IStatus> statuses; + + public SynchronizeQueriesJob(TaskList taskList, TaskDataManager taskDataManager, IRepositoryModel tasksModel, + AbstractRepositoryConnector connector, TaskRepository repository, Set<RepositoryQuery> queries) { + super(Messages.SynchronizeQueriesJob_Synchronizing_Queries + " (" + repository.getRepositoryLabel() + ")"); //$NON-NLS-1$//$NON-NLS-2$ + this.taskList = taskList; + this.taskDataManager = taskDataManager; + this.tasksModel = tasksModel; + this.connector = connector; + this.repository = repository; + this.queries = queries; + this.statuses = new ArrayList<IStatus>(); + } + + @Override + public IStatus run(IProgressMonitor monitor) { + try { + monitor.beginTask(Messages.SynchronizeQueriesJob_Processing, 20 + queries.size() * 20 + 40 + 10); + + Set<ITask> allTasks; + if (!isFullSynchronization()) { + allTasks = new HashSet<ITask>(); + for (RepositoryQuery query : queries) { + allTasks.addAll(query.getChildren()); + } + } else { + allTasks = taskList.getTasks(repository.getRepositoryUrl()); + } + + ObjectSchedulingRule rule = new ObjectSchedulingRule(repository); + try { + Job.getJobManager().beginRule(rule, monitor); + + final Map<String, TaskRelation[]> relationsByTaskId = new HashMap<String, TaskRelation[]>(); + SynchronizationSession session = new SynchronizationSession(taskDataManager) { + @Override + public void putTaskData(ITask task, TaskData taskData) throws CoreException { + boolean changed = connector.hasTaskChanged(repository, task, taskData); + taskDataManager.putUpdatedTaskData(task, taskData, isUser(), this); + if (taskData.isPartial()) { + if (changed && connector.canSynchronizeTask(repository, task)) { + markStale(task); + } + } else { + Collection<TaskRelation> relations = connector.getTaskRelations(taskData); + if (relations != null) { + relationsByTaskId.put(task.getTaskId(), relations.toArray(new TaskRelation[0])); + } + } + } + }; + session.setTaskRepository(repository); + session.setFullSynchronization(isFullSynchronization()); + session.setTasks(Collections.unmodifiableSet(allTasks)); + session.setNeedsPerformQueries(true); + session.setUser(isUser()); + + updateQueryStatus(null); + try { + boolean success = preSynchronization(session, new SubProgressMonitor(monitor, 20)); + + if ((success && session.needsPerformQueries()) || isUser()) { + // synchronize queries, tasks changed within query are added to set of tasks to be synchronized + synchronizeQueries(monitor, session); + } else { + monitor.worked(queries.size() * 20); + } + } finally { + for (RepositoryQuery repositoryQuery : queries) { + repositoryQuery.setSynchronizing(false); + } + taskList.notifySynchronizationStateChanged(queries); + } + + Set<ITask> tasksToBeSynchronized = new HashSet<ITask>(); + if (session.getStaleTasks() != null) { + for (ITask task : session.getStaleTasks()) { + tasksToBeSynchronized.add(task); + ((AbstractTask) task).setSynchronizing(true); + } + } + + // synchronize tasks that were marked by the connector + SynchronizeTasksJob job = new SynchronizeTasksJob(taskList, taskDataManager, tasksModel, connector, + repository, tasksToBeSynchronized); + job.setUser(isUser()); + job.setSession(session); + if (!tasksToBeSynchronized.isEmpty()) { + Policy.checkCanceled(monitor); + IStatus result = job.run(new SubProgressMonitor(monitor, 30)); + if (result == Status.CANCEL_STATUS) { + throw new OperationCanceledException(); + } + statuses.addAll(job.getStatuses()); + } + monitor.subTask(Messages.SynchronizeQueriesJob_Receiving_related_tasks); + job.synchronizedTaskRelations(monitor, relationsByTaskId); + monitor.worked(10); + + session.setChangedTasks(tasksToBeSynchronized); + if (statuses.size() > 0) { + Status status = new MultiStatus(ITasksCoreConstants.ID_PLUGIN, 0, statuses.toArray(new IStatus[0]), + "Query synchronization failed", null); //$NON-NLS-1$ + session.setStatus(status); + } + + // hook into the connector for synchronization time stamp management + postSynchronization(session, new SubProgressMonitor(monitor, 10)); + } finally { + Job.getJobManager().endRule(rule); + } + } catch (OperationCanceledException e) { + return Status.CANCEL_STATUS; + } catch (Exception e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Synchronization failed", e)); //$NON-NLS-1$ + } finally { + monitor.done(); + } + return Status.OK_STATUS; + } + + private void synchronizeQueries(IProgressMonitor monitor, SynchronizationSession session) { + for (RepositoryQuery repositoryQuery : queries) { + Policy.checkCanceled(monitor); + monitor.subTask(MessageFormat.format(Messages.SynchronizeQueriesJob_Synchronizing_query_X, + repositoryQuery.getSummary())); + synchronizeQuery(repositoryQuery, session, new SubProgressMonitor(monitor, 20)); + } + } + + private boolean postSynchronization(SynchronizationSession event, IProgressMonitor monitor) { + try { + Policy.checkCanceled(monitor); + monitor.subTask(Messages.SynchronizeQueriesJob_Updating_repository_state); + if (!isUser()) { + monitor = Policy.backgroundMonitorFor(monitor); + } + connector.postSynchronization(event, monitor); + return true; + } catch (CoreException e) { + updateQueryStatus(e.getStatus()); + return false; + } + } + + private boolean preSynchronization(ISynchronizationSession event, IProgressMonitor monitor) { + try { + Policy.checkCanceled(monitor); + monitor.subTask(Messages.SynchronizeQueriesJob_Querying_repository); + if (!isUser()) { + monitor = Policy.backgroundMonitorFor(monitor); + } + connector.preSynchronization(event, monitor); + return true; + } catch (CoreException e) { + // synchronization is unlikely to succeed, inform user and exit + updateQueryStatus(e.getStatus()); + statuses.add(e.getStatus()); + return false; + } + } + + private void synchronizeQuery(RepositoryQuery repositoryQuery, SynchronizationSession event, + IProgressMonitor monitor) { + TaskCollector collector = new TaskCollector(repositoryQuery, event); + + if (!isUser()) { + monitor = Policy.backgroundMonitorFor(monitor); + } + IStatus result = connector.performQuery(repository, repositoryQuery, collector, event, monitor); + if (result == null || result.isOK()) { + if (collector.getResultCount() >= TaskDataCollector.MAX_HITS) { + StatusHandler.log(new Status(IStatus.WARNING, ITasksCoreConstants.ID_PLUGIN, MAX_HITS_REACHED + "\n" //$NON-NLS-1$ + + repositoryQuery.getSummary())); + } + + Set<ITask> removedChildren = collector.getRemovedChildren(); + if (!removedChildren.isEmpty()) { + taskList.removeFromContainer(repositoryQuery, removedChildren); + } + + repositoryQuery.setLastSynchronizedStamp(new SimpleDateFormat("MMM d, H:mm:ss").format(new Date())); //$NON-NLS-1$ + } else if (result.getSeverity() == IStatus.CANCEL) { + throw new OperationCanceledException(); + } else { + repositoryQuery.setStatus(result); + statuses.add(result); + } + } + + private void updateQueryStatus(final IStatus status) { + for (RepositoryQuery repositoryQuery : queries) { + repositoryQuery.setStatus(status); + } + taskList.notifySynchronizationStateChanged(queries); + } + + public Collection<IStatus> getStatuses() { + return Collections.unmodifiableCollection(statuses); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeRepositoriesJob.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeRepositoriesJob.java new file mode 100644 index 000000000..8d7f8fe7d --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeRepositoriesJob.java @@ -0,0 +1,176 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.sync; + +import java.text.MessageFormat; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.mylyn.commons.net.Policy; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery; +import org.eclipse.mylyn.internal.tasks.core.TaskList; +import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManager; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.mylyn.tasks.core.IRepositoryModel; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.sync.SynchronizationJob; + +/** + * Updates the task list. + * + * @author Steffen Pingel + */ +public class SynchronizeRepositoriesJob extends SynchronizationJob { + + private final TaskList taskList; + + private final TaskDataManager taskDataManager; + + private final IRepositoryManager repositoryManager; + + private Set<TaskRepository> repositories; + + private final Object family = new Object(); + + private final IRepositoryModel tasksModel; + + public SynchronizeRepositoriesJob(TaskList taskList, TaskDataManager taskDataManager, IRepositoryModel tasksModel, + IRepositoryManager repositoryManager) { + super(Messages.SynchronizeRepositoriesJob_Synchronizing_Task_List); + this.taskList = taskList; + this.taskDataManager = taskDataManager; + this.tasksModel = tasksModel; + this.repositoryManager = repositoryManager; + } + + public Collection<TaskRepository> getRepositories() { + return Collections.unmodifiableCollection(repositories); + } + + public void setRepositories(Collection<TaskRepository> repositories) { + if (repositories != null) { + this.repositories = new HashSet<TaskRepository>(repositories); + } else { + this.repositories = null; + } + } + + @Override + public IStatus run(IProgressMonitor monitor) { + // get the current list of repositories + Set<TaskRepository> repositories = this.repositories; + if (repositories == null) { + repositories = new HashSet<TaskRepository>(repositoryManager.getAllRepositories()); + } + try { + monitor.beginTask(Messages.SynchronizeRepositoriesJob_Processing, repositories.size() * 100); + + for (TaskRepository repository : repositories) { + if (monitor.isCanceled()) { + return Status.CANCEL_STATUS; + } + + if (repository.isOffline()) { + monitor.worked(100); + continue; + } + + monitor.setTaskName(MessageFormat.format(Messages.SynchronizeRepositoriesJob_Processing_, + repository.getRepositoryLabel())); + + final AbstractRepositoryConnector connector = repositoryManager.getRepositoryConnector(repository.getConnectorKind()); + Set<RepositoryQuery> queries = taskList.getRepositoryQueries(repository.getRepositoryUrl()); + + if (isUser() || queries.isEmpty()) { + monitor.worked(20); + } else { + // occasionally request update of repository configuration attributes + updateRepositoryConfiguration(repository, connector, new SubProgressMonitor(monitor, 20)); + } + + updateQueries(repository, connector, queries, monitor); + } + + // it's better to remove the job from the progress view instead of having it blocked until all child jobs finish +// if (isUser()) { +// Job.getJobManager().join(family, monitor); +// } + } catch (InterruptedException e) { + return Status.CANCEL_STATUS; + } finally { + monitor.done(); + } + return Status.OK_STATUS; + } + + private void updateQueries(TaskRepository repository, final AbstractRepositoryConnector connector, + Set<RepositoryQuery> queries, IProgressMonitor monitor) { + if (isUser()) { + for (RepositoryQuery query : queries) { + query.setSynchronizing(true); + } + taskList.notifySynchronizationStateChanged(queries); + } + + SynchronizeQueriesJob job = new SynchronizeQueriesJob(taskList, taskDataManager, tasksModel, connector, + repository, queries) { + @Override + public boolean belongsTo(Object family) { + return SynchronizeRepositoriesJob.this.family == family; + } + }; + job.setUser(isUser()); + job.setFullSynchronization(true); + job.setPriority(Job.DECORATE); + if (isUser()) { + job.schedule(); + } else { + job.run(new SubProgressMonitor(monitor, 80)); + } + } + + public Object getFamily() { + return family; + } + + private void updateRepositoryConfiguration(TaskRepository repository, AbstractRepositoryConnector connector, + IProgressMonitor monitor) throws InterruptedException { + try { + if (!isUser()) { + monitor = Policy.backgroundMonitorFor(monitor); + } + monitor.beginTask(MessageFormat.format( + Messages.SynchronizeRepositoriesJob_Updating_repository_configuration_for_X, + repository.getRepositoryUrl()), 100); + if (connector.isRepositoryConfigurationStale(repository, monitor)) { + connector.updateRepositoryConfiguration(repository, monitor); + repository.setConfigurationDate(new Date()); + } + } catch (CoreException e) { + repository.setStatus(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Updating of repository configuration failed", e)); //$NON-NLS-1$ + } finally { + monitor.done(); + } + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeTasksJob.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeTasksJob.java new file mode 100644 index 000000000..ae5551370 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeTasksJob.java @@ -0,0 +1,330 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.core.sync; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.commons.net.Policy; +import org.eclipse.mylyn.internal.tasks.core.AbstractTask; +import org.eclipse.mylyn.internal.tasks.core.AbstractTaskContainer; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.internal.tasks.core.TaskList; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants.MutexSchedulingRule; +import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManager; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.mylyn.tasks.core.IRepositoryModel; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITaskContainer; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.ITask.SynchronizationState; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tasks.core.data.TaskDataCollector; +import org.eclipse.mylyn.tasks.core.data.TaskRelation; +import org.eclipse.mylyn.tasks.core.data.TaskRelation.Direction; +import org.eclipse.mylyn.tasks.core.data.TaskRelation.Kind; +import org.eclipse.mylyn.tasks.core.sync.SynchronizationJob; + +/** + * @author Mik Kersten + * @author Rob Elves + * @author Steffen Pingel + */ +public class SynchronizeTasksJob extends SynchronizationJob { + + private final AbstractRepositoryConnector connector; + + private final TaskDataManager taskDataManager; + + private final TaskList taskList; + + private final Set<ITask> allTasks; + + private final IRepositoryManager repositoryManager; + + private TaskRepository taskRepository; + + private Map<String, TaskRelation[]> relationsByTaskId; + + private boolean updateRelations; + + private final IRepositoryModel tasksModel; + + private SynchronizationSession session; + + private final List<IStatus> statuses; + + public SynchronizeTasksJob(TaskList taskList, TaskDataManager synchronizationManager, IRepositoryModel tasksModel, + AbstractRepositoryConnector connector, TaskRepository taskRepository, Set<ITask> tasks) { + this(taskList, synchronizationManager, tasksModel, connector, (IRepositoryManager) null, tasks); + this.taskRepository = taskRepository; + } + + public SynchronizeTasksJob(TaskList taskList, TaskDataManager synchronizationManager, IRepositoryModel tasksModel, + AbstractRepositoryConnector connector, IRepositoryManager repositoryManager, Set<ITask> tasks) { + super("Synchronizing Tasks (" + tasks.size() + " tasks)"); //$NON-NLS-1$ //$NON-NLS-2$ + this.taskList = taskList; + this.taskDataManager = synchronizationManager; + this.tasksModel = tasksModel; + this.connector = connector; + this.repositoryManager = repositoryManager; + this.allTasks = tasks; + this.statuses = new ArrayList<IStatus>(); + setRule(new MutexSchedulingRule()); + } + + @Override + public IStatus run(IProgressMonitor monitor) { + try { + if (taskRepository == null) { + try { + monitor.beginTask(Messages.SynchronizeTasksJob_Processing, allTasks.size() * 100); + // group tasks by repository + Map<TaskRepository, Set<ITask>> tasksByRepository = new HashMap<TaskRepository, Set<ITask>>(); + for (ITask task : allTasks) { + TaskRepository repository = repositoryManager.getRepository(task.getConnectorKind(), + task.getRepositoryUrl()); + Set<ITask> tasks = tasksByRepository.get(repository); + if (tasks == null) { + tasks = new HashSet<ITask>(); + tasksByRepository.put(repository, tasks); + } + tasks.add(task); + } + // synchronize tasks for each repositories + for (TaskRepository taskRepository : tasksByRepository.keySet()) { + setName(MessageFormat.format(Messages.SynchronizeTasksJob_Synchronizing_Tasks__X_, + taskRepository.getRepositoryLabel())); + this.taskRepository = taskRepository; + Set<ITask> repositoryTasks = tasksByRepository.get(taskRepository); + run(repositoryTasks, new SubProgressMonitor(monitor, repositoryTasks.size() * 100)); + } + } finally { + monitor.done(); + } + } else { + run(allTasks, monitor); + } + } catch (OperationCanceledException e) { + for (ITask task : allTasks) { + ((AbstractTask) task).setSynchronizing(false); + taskList.notifyElementChanged(task); + } + return Status.CANCEL_STATUS; + } + return Status.OK_STATUS; + } + + private void run(Set<ITask> tasks, IProgressMonitor monitor) { + relationsByTaskId = new HashMap<String, TaskRelation[]>(); + updateRelations = true; + runInternal(tasks, monitor); + synchronizedTaskRelations(monitor, relationsByTaskId); + } + + public void synchronizedTaskRelations(IProgressMonitor monitor, Map<String, TaskRelation[]> relationsByTaskId) { + updateRelations = false; + for (String taskId : relationsByTaskId.keySet()) { + ITask parentTask = taskList.getTask(taskRepository.getRepositoryUrl(), taskId); + if (parentTask instanceof ITaskContainer) { + Set<ITask> removedChildTasks = new HashSet<ITask>(((ITaskContainer) parentTask).getChildren()); + + TaskRelation[] relations = relationsByTaskId.get(taskId); + for (TaskRelation relation : relations) { + if (relation.getDirection() == Direction.OUTWARD && relation.getKind() == Kind.CONTAINMENT) { + ITask task = taskList.getTask(taskRepository.getRepositoryUrl(), relation.getTaskId()); + if (task == null) { + try { + task = synchronizeTask(monitor, relation.getTaskId()); + } catch (CoreException e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Synchronization failed", e)); //$NON-NLS-1$ + } + } else { + removedChildTasks.remove(task); + } + + if (task != null) { + taskList.addTask(task, (AbstractTaskContainer) parentTask); + } + } + } + + for (ITask task : removedChildTasks) { + taskList.removeFromContainer((AbstractTaskContainer) parentTask, task); + } + } + } + } + + private void runInternal(Set<ITask> tasks, IProgressMonitor monitor) { + try { + monitor.beginTask(Messages.SynchronizeTasksJob_Processing, tasks.size() * 100); + if (canGetMultiTaskData(taskRepository)) { + try { + for (ITask task : tasks) { + resetStatus(task); + } + synchronizeTasks(new SubProgressMonitor(monitor, tasks.size() * 100), taskRepository, tasks); + } catch (CoreException e) { + for (ITask task : tasks) { + updateStatus(taskRepository, task, e.getStatus()); + } + } + } else { + for (ITask task : tasks) { + Policy.checkCanceled(monitor); + resetStatus(task); + try { + synchronizeTask(new SubProgressMonitor(monitor, 100), task); + } catch (CoreException e) { + updateStatus(taskRepository, task, e.getStatus()); + } + } + } + } catch (OperationCanceledException e) { + throw e; + } catch (Exception e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Synchronization failed", e)); //$NON-NLS-1$ + } finally { + monitor.done(); + } + } + + private boolean canGetMultiTaskData(TaskRepository taskRepository) { + AbstractTaskDataHandler taskDataHandler = connector.getTaskDataHandler(); + return taskDataHandler != null && taskDataHandler.canGetMultiTaskData(taskRepository); + } + + private void synchronizeTask(IProgressMonitor monitor, ITask task) throws CoreException { + monitor.subTask(MessageFormat.format(Messages.SynchronizeTasksJob_Receiving_task_X, task.getSummary())); + resetStatus(task); + if (!isUser()) { + monitor = Policy.backgroundMonitorFor(monitor); + } + String taskId = task.getTaskId(); + TaskData taskData = connector.getTaskData(taskRepository, taskId, monitor); + if (taskData != null) { + updateFromTaskData(taskRepository, task, taskData); + return; + } + throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Connector failed to return task data for task \"" + task + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ + } + + private ITask synchronizeTask(IProgressMonitor monitor, String taskId) throws CoreException { + monitor.subTask(MessageFormat.format(Messages.SynchronizeTasksJob_Receiving_task_X, taskId)); + if (!isUser()) { + monitor = Policy.backgroundMonitorFor(monitor); + } + + TaskData taskData = connector.getTaskData(taskRepository, taskId, monitor); + if (taskData != null) { + return createFromTaskData(taskRepository, taskId, taskData); + } + + throw new CoreException(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Connector failed to return task data for task \"" + taskId + "\"")); //$NON-NLS-1$ //$NON-NLS-2$ + } + + private void resetStatus(ITask task) { + ((AbstractTask) task).setStatus(null); + taskList.notifySynchronizationStateChanged(task); + } + + private void synchronizeTasks(IProgressMonitor monitor, final TaskRepository repository, Set<ITask> tasks) + throws CoreException { + monitor.subTask(MessageFormat.format(Messages.SynchronizeTasksJob_Receiving_X_tasks_from_X, tasks.size(), + repository.getRepositoryLabel())); + + final Map<String, ITask> idToTask = new HashMap<String, ITask>(); + for (ITask task : tasks) { + idToTask.put(task.getTaskId(), task); + } + + TaskDataCollector collector = new TaskDataCollector() { + @Override + public void accept(TaskData taskData) { + ITask task = idToTask.remove(taskData.getTaskId()); + if (task != null) { + updateFromTaskData(repository, task, taskData); + } + } + }; + + if (!isUser()) { + monitor = Policy.backgroundMonitorFor(monitor); + } + Set<String> taskIds = Collections.unmodifiableSet(new HashSet<String>(idToTask.keySet())); + connector.getTaskDataHandler().getMultiTaskData(repository, taskIds, collector, monitor); + } + + private void updateFromTaskData(TaskRepository taskRepository, ITask task, TaskData taskData) { + try { + taskDataManager.putUpdatedTaskData(task, taskData, isUser(), getSession()); + if (updateRelations) { + Collection<TaskRelation> relations = connector.getTaskRelations(taskData); + if (relations != null) { + relationsByTaskId.put(task.getTaskId(), relations.toArray(new TaskRelation[0])); + } + } + } catch (CoreException e) { + updateStatus(taskRepository, task, e.getStatus()); + } + } + + private ITask createFromTaskData(TaskRepository taskRepository, String taskId, TaskData taskData) + throws CoreException { + ITask task = tasksModel.createTask(taskRepository, taskData.getTaskId()); + ((AbstractTask) task).setSynchronizationState(SynchronizationState.INCOMING_NEW); + taskDataManager.putUpdatedTaskData(task, taskData, isUser(), getSession()); + return task; + } + + private void updateStatus(TaskRepository repository, ITask task, IStatus status) { + statuses.add(status); + ((AbstractTask) task).setStatus(status); + if (!isUser()) { + ((AbstractTask) task).setSynchronizing(false); + } + taskList.notifyElementChanged(task); + } + + public SynchronizationSession getSession() { + return session; + } + + public void setSession(SynchronizationSession session) { + this.session = session; + } + + public Collection<IStatus> getStatuses() { + return Collections.unmodifiableCollection(statuses); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/messages.properties b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/messages.properties new file mode 100644 index 000000000..fb2d72551 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/messages.properties @@ -0,0 +1,24 @@ +SubmitTaskAttachmentJob_Sending_data=Sending data +SubmitTaskAttachmentJob_Submitting_attachment=Submitting attachment +SubmitTaskAttachmentJob_Updating_task=Updating task + +SubmitTaskJob_Receiving_data=Receiving data +SubmitTaskJob_Sending_data=Sending data +SubmitTaskJob_Submitting_task=Submitting task + +SynchronizeQueriesJob_Max_allowed_number_of_hits_returned_exceeded=Max allowed number of hits returned exceeded. Some hits may not be displayed. Please narrow query scope. +SynchronizeQueriesJob_Processing=Processing +SynchronizeQueriesJob_Querying_repository=Querying repository +SynchronizeQueriesJob_Receiving_related_tasks=Receiving related tasks +SynchronizeQueriesJob_Synchronizing_Queries=Synchronizing Queries +SynchronizeQueriesJob_Synchronizing_query_X=Synchronizing query: {0} +SynchronizeQueriesJob_Updating_repository_state=Updating repository state + +SynchronizeRepositoriesJob_Processing=Processing +SynchronizeRepositoriesJob_Processing_=Processing {0} +SynchronizeRepositoriesJob_Synchronizing_Task_List=Synchronizing Task List +SynchronizeRepositoriesJob_Updating_repository_configuration_for_X=Updating repository configuration for {0} +SynchronizeTasksJob_Processing=Processing +SynchronizeTasksJob_Receiving_task_X=Receiving task {0} +SynchronizeTasksJob_Synchronizing_Tasks__X_=Synchronizing Tasks ({0}) +SynchronizeTasksJob_Receiving_X_tasks_from_X=Receiving {0} tasks from {1} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractDuplicateDetector.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractDuplicateDetector.java new file mode 100644 index 000000000..de006f90d --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractDuplicateDetector.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.mylyn.tasks.core.data.TaskData; + +/** + * Extend to provide task duplicate detection facilities to the task editor (e.g. Java stack trace matching). + * + * @author Gail Murphy + * @author Robert Elves + * @author Steffen Pingel + * @since 3.0 + */ +public abstract class AbstractDuplicateDetector { + + private String name; + + private String connectorKind; + + public abstract IRepositoryQuery getDuplicatesQuery(TaskRepository repository, TaskData taskData) + throws CoreException; + + public void setName(String name) { + this.name = name; + } + + public void setConnectorKind(String kind) { + this.connectorKind = kind; + } + + public String getName() { + return this.name; + } + + public String getConnectorKind() { + return this.connectorKind; + } + + public boolean canQuery(TaskData taskData) { + return true; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractRepositoryConnector.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractRepositoryConnector.java new file mode 100644 index 000000000..7ed6a76f5 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractRepositoryConnector.java @@ -0,0 +1,281 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import java.util.Collection; +import java.util.Date; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskAttachmentHandler; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tasks.core.data.TaskDataCollector; +import org.eclipse.mylyn.tasks.core.data.TaskMapper; +import org.eclipse.mylyn.tasks.core.data.TaskRelation; +import org.eclipse.mylyn.tasks.core.sync.ISynchronizationSession; + +/** + * Encapsulates common operations that can be performed on a task repository. Extend to connect with a Java API or WS + * API for accessing the repository. + * + * Only methods that take a progress monitor can do network I/O. + * + * @author Mik Kersten + * @author Rob Elves + * @author Shawn Minto + * @since 2.0 + */ +public abstract class AbstractRepositoryConnector { + + private static final long REPOSITORY_CONFIGURATION_UPDATE_INTERVAL = 24 * 60 * 60 * 1000; + + /** + * Returns true, if the connector provides a wizard for creating new tasks. + * + * @since 2.0 + */ + // TODO move this to ConnectorUi.hasNewTaskWizard() + public abstract boolean canCreateNewTask(TaskRepository repository); + + /** + * Returns true, if the connector supports retrieval of tasks based on String keys. + * + * @since 2.0 + */ + public abstract boolean canCreateTaskFromKey(TaskRepository repository); + + /** + * @since 3.0 + */ + public boolean canQuery(TaskRepository repository) { + return true; + } + + /** + * @since 3.0 + */ + public boolean canSynchronizeTask(TaskRepository taskRepository, ITask task) { + return true; + } + + /** + * @return the unique kind of the repository, e.g. "bugzilla" + * @since 2.0 + */ + public abstract String getConnectorKind(); + + /** + * The connector's summary i.e. "JIRA (supports 3.3.1 and later)" + * + * @since 2.0 + */ + public abstract String getLabel(); + + /** + * Can return null if URLs are not used to identify tasks. + */ + public abstract String getRepositoryUrlFromTaskUrl(String taskFullUrl); + + /** + * Returns a short label for the connector, e.g. Bugzilla. + * + * @since 2.3 + */ + public String getShortLabel() { + String label = getLabel(); + if (label == null) { + return null; + } + + int i = label.indexOf("("); //$NON-NLS-1$ + if (i != -1) { + return label.substring(0, i).trim(); + } + + i = label.indexOf(" "); //$NON-NLS-1$ + if (i != -1) { + return label.substring(0, i).trim(); + } + + return label; + } + + /** + * @since 3.0 + */ + public AbstractTaskAttachmentHandler getTaskAttachmentHandler() { + return null; + } + + /** + * @since 3.0 + */ + public abstract TaskData getTaskData(TaskRepository taskRepository, String taskId, IProgressMonitor monitor) + throws CoreException; + + /** + * @since 3.0 + */ + public AbstractTaskDataHandler getTaskDataHandler() { + return null; + } + + /** + * @since 2.0 + */ + public abstract String getTaskIdFromTaskUrl(String taskFullUrl); + + /** + * Used for referring to the task in the UI. + */ + public String getTaskIdPrefix() { + return "task"; //$NON-NLS-1$ + } + + /** + * @since 2.0 + */ + public String[] getTaskIdsFromComment(TaskRepository repository, String comment) { + return null; + } + + /** + * @since 3.0 + */ + public ITaskMapping getTaskMapping(TaskData taskData) { + return new TaskMapper(taskData); + } + + /** + * Connectors can override to return other tasks associated with this task. + * + * @since 3.0 + */ + public Collection<TaskRelation> getTaskRelations(TaskData taskData) { + return null; + } + + /** + * @since 2.0 + */ + public abstract String getTaskUrl(String repositoryUrl, String taskId); + + /** + * @since 3.0 + */ + public abstract boolean hasTaskChanged(TaskRepository taskRepository, ITask task, TaskData taskData); + + /** + * @since 3.0 + */ + public boolean hasLocalCompletionState(TaskRepository taskRepository, ITask task) { + return false; + } + + /** + * @since 3.0 + */ + public boolean hasRepositoryDueDate(TaskRepository taskRepository, ITask task, TaskData taskData) { + return false; + } + + /** + * Default implementation returns true every 24hrs. + * + * @return true to indicate that the repository configuration is stale and requires update + * @since 3.0 + */ + public boolean isRepositoryConfigurationStale(TaskRepository repository, IProgressMonitor monitor) + throws CoreException { + Date configDate = repository.getConfigurationDate(); + if (configDate != null) { + return (new Date().getTime() - configDate.getTime()) > REPOSITORY_CONFIGURATION_UPDATE_INTERVAL; + } + return true; + } + + /** + * @since 2.0 + */ + public boolean isUserManaged() { + return true; + } + + /** + * Runs <code>query</code> on <code>repository</code>, results are passed to <code>collector</code>. If a repository + * does not return the full task data for a result, {@link TaskData#isPartial()} will return true. + * + * <p> + * Implementors must complete executing <code>query</code> before returning from this method. + * + * @param repository + * task repository to run query against + * @param query + * query to run + * @param collector + * callback for returning results + * @param session + * provides additional information for running the query, may be <code>null</code> + * @param monitor + * for reporting progress + * @return {@link Status#OK_STATUS} in case of success, an error status otherwise + * @throws OperationCanceledException + * if the query was canceled + * @since 3.0 + */ + public abstract IStatus performQuery(TaskRepository repository, IRepositoryQuery query, + TaskDataCollector collector, ISynchronizationSession session, IProgressMonitor monitor); + + /** + * Hook into the synchronization process. + * + * @since 3.0 + */ + public void postSynchronization(ISynchronizationSession event, IProgressMonitor monitor) throws CoreException { + try { + monitor.beginTask("", 1); //$NON-NLS-1$ + } finally { + monitor.done(); + } + } + + /** + * Hook into the synchronization process. + * + * @since 3.0 + */ + public void preSynchronization(ISynchronizationSession event, IProgressMonitor monitor) throws CoreException { + try { + monitor.beginTask("", 1); //$NON-NLS-1$ + } finally { + monitor.done(); + } + } + + /** + * Reset and update the repository attributes from the server (e.g. products, components) + * + * @since 3.0 + */ + public abstract void updateRepositoryConfiguration(TaskRepository taskRepository, IProgressMonitor monitor) + throws CoreException; + + /** + * @since 3.0 + */ + public abstract void updateTaskFromTaskData(TaskRepository taskRepository, ITask task, TaskData taskData); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractTaskListMigrator.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractTaskListMigrator.java new file mode 100644 index 000000000..ab18983ac --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractTaskListMigrator.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import java.util.Set; + +import org.w3c.dom.Element; + +/** + * @author Steffen Pingel + * @since 3.0 + */ +public abstract class AbstractTaskListMigrator { + + public static final String KEY_QUERY = "Query"; //$NON-NLS-1$ + + public static final String KEY_TASK = "Task"; //$NON-NLS-1$ + + public static final String KEY_LAST_MOD_DATE = "LastModified"; //$NON-NLS-1$ + + public abstract String getTaskElementName(); + + public abstract Set<String> getQueryElementNames(); + + public abstract void migrateQuery(IRepositoryQuery query, Element element); + + public abstract void migrateTask(ITask task, Element element); + + public abstract String getConnectorKind(); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IAttributeContainer.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IAttributeContainer.java new file mode 100644 index 000000000..cf2f66d33 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IAttributeContainer.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import java.util.Map; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IAttributeContainer { + + public abstract String getAttribute(String key); + + public abstract void setAttribute(String key, String value); + + public abstract Map<String, String> getAttributes(); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryElement.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryElement.java new file mode 100644 index 000000000..c8e5b8b4b --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryElement.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import org.eclipse.core.runtime.IAdaptable; + +/** + * @author Mik Kersten + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IRepositoryElement extends Comparable<IRepositoryElement>, IAdaptable { + + /** + * Returns a readable description of the element. + */ + public abstract String getSummary(); + + /** + * Returns an identifier for unique to where it resides. For tasks this is an identifier unique to the repository in + * which the tasks resides, such as the local machine or a web service. For elements in the Task List such as + * queries or categories, this identifier may only be unique to that Task List. + */ + public abstract String getHandleIdentifier(); + + /** + * Used for elements that reside in web services and can be used for URL-based access to resources on the local + * machine. Optional, can be null. + */ + public abstract String getUrl(); + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryListener.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryListener.java new file mode 100644 index 000000000..ae348601a --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryListener.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +/** + * Notified of change to the life-cycle of task repositories. + * + * @author Mik Kersten + * @since 3.0 + */ +public interface IRepositoryListener { + + /** + * A task repository has been added. + * + * @since 3.0 + */ + public abstract void repositoryAdded(TaskRepository repository); + + /** + * A task repository has been removed. + * + * @since 3.0 + */ + public abstract void repositoryRemoved(TaskRepository repository); + + /** + * The settings of a repository have been updated. + * + * @since 3.0 + */ + public abstract void repositorySettingsChanged(TaskRepository repository); + + /** + * TODO: Refactor into general delta notification + * + * @since 3.0 + */ + public abstract void repositoryUrlChanged(TaskRepository repository, String oldUrl); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryManager.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryManager.java new file mode 100644 index 000000000..719a3cffc --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryManager.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IRepositoryManager { + + public abstract void addListener(IRepositoryListener listener); + + public abstract void addRepository(TaskRepository repository); + + public abstract List<TaskRepository> getAllRepositories(); + + public abstract Set<TaskRepository> getRepositories(String connectorKind); + + public abstract TaskRepository getRepository(String connectorKind, String repositoryUrl); + + public abstract AbstractRepositoryConnector getRepositoryConnector(String connectorKind); + + public abstract Collection<AbstractRepositoryConnector> getRepositoryConnectors(); + + public abstract void removeListener(IRepositoryListener listener); + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryModel.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryModel.java new file mode 100644 index 000000000..e2add13f4 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryModel.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +/** + * @since 3.0 + * @author Steffen Pingel + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IRepositoryModel { + + /** + * @since 3.0 + */ + public abstract IRepositoryQuery createRepositoryQuery(TaskRepository taskRepository); + + /** + * @since 3.0 + */ + public abstract ITask createTask(TaskRepository taskRepository, String taskId); + + /** + * @since 3.0 + */ + public abstract ITask getTask(TaskRepository taskRepository, String taskId); + + /** + * Gets a task by its {@link ITask#getTaskKey() key}. + * + * @return the task or null if no such task was found + * @since 3.2 + */ + public abstract ITask getTaskByKey(TaskRepository repository, String taskKey); + + /** + * @since 3.0 + */ + public abstract ITask getTask(String handle); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryPerson.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryPerson.java new file mode 100644 index 000000000..8a005fedc --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryPerson.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IRepositoryPerson { + + /** + * @since 3.0 + */ + public abstract String getConnectorKind(); + + /** + * @since 3.0 + */ + public abstract String getName(); + + /** + * @since 3.0 + */ + public abstract String getPersonId(); + + /** + * @since 3.0 + */ + public abstract String getRepositoryUrl(); + + /** + * @since 3.0 + */ + public abstract TaskRepository getTaskRepository(); + + /** + * @since 3.0 + */ + public abstract void setName(String name); + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryQuery.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryQuery.java new file mode 100644 index 000000000..88a7dabb5 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryQuery.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +/** + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IRepositoryQuery extends IAttributeContainer { + + /** + * @since 3.0 + */ + public abstract String getConnectorKind(); + + /** + * @since 3.0 + */ + public abstract String getRepositoryUrl(); + + /** + * @since 3.0 + */ + public abstract String getUrl(); + + /** + * @since 3.0 + */ + public abstract void setUrl(String url); + + /** + * @since 3.0 + */ + public abstract String getSummary(); + + /** + * @since 3.0 + */ + public abstract void setSummary(String summary); + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITask.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITask.java new file mode 100644 index 000000000..1d5896421 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITask.java @@ -0,0 +1,338 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import java.util.Date; + +import org.eclipse.mylyn.internal.tasks.core.Messages; + +/** + * @author Mik Kersten + * @author Steffen Pingel + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ITask extends IRepositoryElement, IAttributeContainer { + + /** + * @since 3.0 + */ + public enum SynchronizationState { + CONFLICT, INCOMING, INCOMING_NEW, OUTGOING, OUTGOING_NEW, SYNCHRONIZED; + + /** + * @since 3.0 + */ + public boolean isIncoming() { + switch (this) { + case INCOMING: + case INCOMING_NEW: + case CONFLICT: + return true; + default: + return false; + } + } + + /** + * @since 3.0 + */ + public boolean isOutgoing() { + switch (this) { + case OUTGOING: + case OUTGOING_NEW: + case CONFLICT: + return true; + default: + return false; + } + } + + /** + * @since 3.0 + */ + public boolean isSynchronized() { + switch (this) { + case SYNCHRONIZED: + return true; + default: + return false; + } + } + } + + /** + * @since 3.0 + */ + public enum PriorityLevel { + P1, P2, P3, P4, P5; + + @Override + public String toString() { + switch (this) { + case P1: + return "P1"; //$NON-NLS-1$ + case P2: + return "P2"; //$NON-NLS-1$ + case P3: + return "P3"; //$NON-NLS-1$ + case P4: + return "P4"; //$NON-NLS-1$ + case P5: + return "P5"; //$NON-NLS-1$ + default: + return "P3"; //$NON-NLS-1$ + } + } + + /** + * @since 3.0 + */ + public String getDescription() { + switch (this) { + case P1: + return Messages.PriorityLevel_Very_High; + case P2: + return Messages.PriorityLevel_High; + case P3: + return Messages.PriorityLevel_Normal; + case P4: + return Messages.PriorityLevel_Low; + case P5: + return Messages.PriorityLevel_Very_Low; + default: + return ""; //$NON-NLS-1$ + } + } + + /** + * @since 3.0 + */ + public static PriorityLevel fromLevel(int level) { + if (level <= 1) { + return P1; + } + if (level == 2) { + return P2; + } + if (level == 3) { + return P3; + } + if (level == 4) { + return P4; + } + if (level >= 5) { + return P5; + } + return getDefault(); + } + + /** + * @since 3.0 + */ + public static PriorityLevel fromString(String string) { + if ("P1".equals(string)) { //$NON-NLS-1$ + return P1; + } + if ("P2".equals(string)) { //$NON-NLS-1$ + return P2; + } + if ("P3".equals(string)) { //$NON-NLS-1$ + return P3; + } + if ("P4".equals(string)) { //$NON-NLS-1$ + return P4; + } + if ("P5".equals(string)) { //$NON-NLS-1$ + return P5; + } + return getDefault(); + } + + /** + * @since 3.0 + */ + public static PriorityLevel fromDescription(String string) { + if (string == null) { + return null; + } + if (string.equals(Messages.PriorityLevel_Very_High)) { + return P1; + } + if (string.equals(Messages.PriorityLevel_High)) { + return P2; + } + if (string.equals(Messages.PriorityLevel_Normal)) { + return P3; + } + if (string.equals(Messages.PriorityLevel_Low)) { + return P4; + } + if (string.equals(Messages.PriorityLevel_Very_Low)) { + return P5; + } + return getDefault(); + } + + /** + * @since 3.0 + */ + public static PriorityLevel getDefault() { + return P3; + } + } + + /** + * Returns the date that the task was completed. + * + * @since 3.0 + */ + public abstract Date getCompletionDate(); + + /** + * Returns the identifier that uniquely distinguishes the repository connector associated with this task. + * + * @since 3.0 + */ + public abstract String getConnectorKind(); + + /** + * Returns the date that this task was created. + * + * @since 3.0 + */ + public abstract Date getCreationDate(); + + /** + * Returns the date after which this task will become overdue. + * + * @since 3.0 + */ + public abstract Date getDueDate(); + + /** + * @since 3.0 + */ + public abstract String getHandleIdentifier(); + + /** + * Returns the date that the repository contents of this task were last modified. + * + * @since 3.0 + */ + public abstract Date getModificationDate(); + + /** + * @since 3.0 + */ + public abstract String getOwner(); + + /** + * @since 3.0 + */ + public abstract String getPriority(); + + /** + * @since 3.0 + */ + public abstract String getRepositoryUrl(); + + /** + * @since 3.0 + */ + public abstract String getSummary(); + + /** + * @since 3.0 + */ + public abstract SynchronizationState getSynchronizationState(); + + /** + * @since 3.0 + */ + public abstract String getTaskId(); + + /** + * User identifiable key for the task to be used in UI facilities such as label displays and hyperlinked references. + * Can return the same as the ID (e.g. in the case of Bugzilla). Can return null if no such label exists. + * + * @since 3.0 + */ + public abstract String getTaskKey(); + + /** + * @since 3.0 + */ + public abstract String getTaskKind(); + + /** + * @since 3.0 + */ + public abstract boolean isActive(); + + /** + * @since 3.0 + */ + public abstract boolean isCompleted(); + + /** + * @since 3.0 + */ + public abstract void setCompletionDate(Date completionDate); + + /** + * @since 3.0 + */ + public abstract void setCreationDate(Date date); + + /** + * @since 3.0 + */ + public abstract void setDueDate(Date date); + + /** + * @since 3.0 + */ + public abstract void setModificationDate(Date modificationDate); + + /** + * @since 3.0 + */ + public abstract void setOwner(String owner); + + /** + * @since 3.0 + */ + public abstract void setPriority(String priority); + + /** + * @since 3.0 + */ + public abstract void setSummary(String summary); + + /** + * @since 3.0 + */ + public abstract void setTaskKind(String kind); + + /** + * @since 3.0 + */ + public abstract void setUrl(String taskUrl); + + /** + * @since 3.0 + */ + public abstract void setTaskKey(String taskKey); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivationListener.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivationListener.java new file mode 100644 index 000000000..a57f290a4 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivationListener.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +/** + * @author Rob Elves + * @since 3.0 + */ +public interface ITaskActivationListener { + + /** + * @since 3.0 + */ + public abstract void preTaskActivated(ITask task); + + /** + * @since 3.0 + */ + public abstract void preTaskDeactivated(ITask task); + + /** + * @since 3.0 + */ + public abstract void taskActivated(ITask task); + + /** + * @since 3.0 + */ + public abstract void taskDeactivated(ITask task); + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivityListener.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivityListener.java new file mode 100644 index 000000000..fb0e56c0d --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivityListener.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +/** + * Notified of task activity changes. + * + * @author Mik Kersten + * @author Rob Elves + * @author Shawn Minto + * @since 2.0 + */ +public interface ITaskActivityListener { + + /** + * @since 3.0 + */ + public abstract void activityReset(); + + /** + * Warning: This is called frequently (i.e. every 15s) Implementers are responsible for launching jobs for long + * running activity. + * + * @since 3.0 + */ + public abstract void elapsedTimeUpdated(ITask task, long newElapsedTime); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivityManager.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivityManager.java new file mode 100644 index 000000000..02dd0cacf --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivityManager.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import java.util.Calendar; +import java.util.Set; + +import org.eclipse.mylyn.internal.tasks.core.AbstractTask; + +/** + * @author Rob Elves + * @since 3.0 + */ +public interface ITaskActivityManager { + + /** + * activate the given <code>task</code> + */ + public abstract void activateTask(ITask task); + + /** + * deactivate the currently active task (if any). There are no negative side effects if this method is called when + * no task is active + */ + public abstract void deactivateActiveTask(); + + /** + * deactivate the given task + */ + public abstract void deactivateTask(ITask task); + + /** + * returns all tasks that where active between <code>start</code> and <code>end</end> (exclusive) + * both ranges are floored to the hour + */ + public abstract Set<AbstractTask> getActiveTasks(Calendar start, Calendar end); + + /** + * @return the currently active task if any + */ + public abstract ITask getActiveTask(); + + /** + * returns all tasks with a due date set + */ + public abstract Set<ITask> getAllDueTasks(); + + /** + * returns all tasks due between the given dates + */ + public abstract Set<ITask> getDueTasks(Calendar start, Calendar end); + + /** total elapsed time based on activation history */ + public abstract long getElapsedTime(ITask task); + + /** + * return the total elapsed time based on activation history between <code>start</code> and <code>end</code> If task + * is null, the elapsed time for the range with no task active is returned + */ + public abstract long getElapsedTime(ITask task, Calendar start, Calendar end); + + public abstract void addActivityListener(ITaskActivityListener listener); + + public abstract void removeActivityListener(ITaskActivityListener listener); + + public abstract void addActivationListener(ITaskActivationListener listener); + + public abstract void removeActivationListener(ITaskActivationListener listener); + + /** + * @param task + * cannot be null + * @return whether the task is the single currently active task + */ + public abstract boolean isActive(ITask task); +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskAttachment.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskAttachment.java new file mode 100644 index 000000000..f29862b0b --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskAttachment.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import java.util.Date; + +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ITaskAttachment { + + /** + * @since 3.0 + */ + public abstract IRepositoryPerson getAuthor(); + + /** + * @since 3.0 + */ + public abstract String getComment(); + + /** + * @since 3.0 + */ + public abstract String getConnectorKind(); + + /** + * @since 3.0 + */ + public abstract String getContentType(); + + /** + * @since 3.0 + */ + public abstract Date getCreationDate(); + + /** + * @since 3.0 + */ + public abstract String getDescription(); + + /** + * @since 3.0 + */ + public abstract String getFileName(); + + /** + * @since 3.0 + */ + public abstract long getLength(); + + /** + * @since 3.0 + */ + public abstract String getRepositoryUrl(); + + /** + * @since 3.0 + */ + public abstract ITask getTask(); + + /** + * @since 3.0 + */ + public abstract TaskAttribute getTaskAttribute(); + + /** + * @since 3.0 + */ + public abstract TaskRepository getTaskRepository(); + + /** + * @since 3.0 + */ + public abstract String getUrl(); + + /** + * @since 3.0 + */ + public abstract boolean isDeprecated(); + + /** + * @since 3.0 + */ + public abstract boolean isPatch(); + + /** + * @since 3.0 + */ + public abstract void setAuthor(IRepositoryPerson author); + + /** + * @since 3.0 + */ + public abstract void setContentType(String contentType); + + /** + * @since 3.0 + */ + public abstract void setCreationDate(Date creationDate); + + /** + * @since 3.0 + */ + public abstract void setDeprecated(boolean deprecated); + + /** + * @since 3.0 + */ + public abstract void setDescription(String description); + + /** + * @since 3.0 + */ + public abstract void setFileName(String fileName); + + /** + * @since 3.0 + */ + public abstract void setLength(long length); + + /** + * @since 3.0 + */ + public abstract void setPatch(boolean patch); + + /** + * @since 3.0 + */ + public abstract void setUrl(String url); + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskComment.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskComment.java new file mode 100644 index 000000000..e20040134 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskComment.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import java.util.Date; + +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; + +/** + * A comment posted by a user on a task. + * + * @author Steffen Pingel + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ITaskComment { + + /** + * @since 3.0 + */ + public abstract IRepositoryPerson getAuthor(); + + /** + * @since 3.0 + */ + public abstract String getConnectorKind(); + + /** + * @since 3.0 + */ + public abstract Date getCreationDate(); + + /** + * @since 3.0 + */ + public abstract int getNumber(); + + /** + * @since 3.0 + */ + public abstract String getRepositoryUrl(); + + /** + * @since 3.0 + */ + public abstract ITask getTask(); + + /** + * @since 3.0 + */ + public abstract TaskAttribute getTaskAttribute(); + + /** + * @since 3.0 + */ + public abstract TaskRepository getTaskRepository(); + + /** + * @since 3.0 + */ + public abstract String getText(); + + /** + * @since 3.0 + */ + public abstract String getUrl(); + + /** + * @since 3.0 + */ + public abstract void setAuthor(IRepositoryPerson author); + + /** + * @since 3.0 + */ + public abstract void setCreationDate(Date creationDate); + + /** + * @since 3.0 + */ + public abstract void setNumber(int number); + + /** + * @since 3.0 + */ + public abstract void setText(String text); + + /** + * @since 3.0 + */ + public abstract void setUrl(String url); + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskContainer.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskContainer.java new file mode 100644 index 000000000..8954ae97d --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskContainer.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import java.util.Collection; + +/** + * @author Mik Kersten + * @author Steffen Pingel + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ITaskContainer { + + /** + * Returns the children of this task, as defined by a containment hierarchy such as the Task List's categories, + * queries and substasks. Never returns null. + * + * @since 3.0 + */ + public abstract Collection<ITask> getChildren(); + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskMapping.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskMapping.java new file mode 100644 index 000000000..af7d4eb6b --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskMapping.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import java.util.Date; +import java.util.List; + +import org.eclipse.mylyn.tasks.core.ITask.PriorityLevel; +import org.eclipse.mylyn.tasks.core.data.TaskData; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. Extend {@link TaskMapping} instead. + */ +public interface ITaskMapping { + + public void merge(ITaskMapping source); + + public abstract List<String> getCc(); + + public abstract Date getCompletionDate(); + + public abstract String getComponent(); + + public abstract Date getCreationDate(); + + public abstract String getDescription(); + + public abstract Date getDueDate(); + + public abstract List<String> getKeywords(); + + public abstract Date getModificationDate(); + + public abstract String getOwner(); + + public abstract String getPriority(); + + public abstract PriorityLevel getPriorityLevel(); + + public abstract String getProduct(); + + public abstract String getReporter(); + + public abstract String getResolution(); + + /** + * @since 3.2 + */ + public abstract String getSeverity(); + + public abstract String getSummary(); + + public abstract String getStatus(); + + public abstract TaskData getTaskData(); + + public abstract String getTaskKey(); + + public abstract String getTaskKind(); + + public abstract String getTaskStatus(); + + public abstract String getTaskUrl(); + + /** + * @since 3.2 + */ + public abstract String getVersion(); + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryResponse.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryResponse.java new file mode 100644 index 000000000..a9afe19a4 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryResponse.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +/** + * Clients may subclass. + * + * @author Steffen Pingel + * @since 3.0 + */ +public class RepositoryResponse { + + public enum ResponseKind { + TASK_CREATED, TASK_UPDATED; + }; + + private final String taskId; + + private final ResponseKind reposonseKind; + + public RepositoryResponse(ResponseKind reposonseKind, String taskId) { + this.reposonseKind = reposonseKind; + this.taskId = taskId; + } + + public RepositoryResponse() { + this(null, null); + } + + public String getTaskId() { + return taskId; + } + + public ResponseKind getReposonseKind() { + return reposonseKind; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryStatus.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryStatus.java new file mode 100644 index 000000000..c08c9480b --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryStatus.java @@ -0,0 +1,198 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.osgi.util.NLS; + +/** + * Utility for working and capturing status specific to repository connections. + * + * @author Rob Elves + * @author Steffen Pingel + * @since 2.0 + */ +public class RepositoryStatus extends Status { + + public final static int ERROR_IO = 5; + + public final static int ERROR_NETWORK = 11; + + public final static int ERROR_PERMISSION_DENIED = 12; + + /** + * requires construction with repositoryUrl and error message + */ + public final static int ERROR_REPOSITORY = 1; + + public final static int ERROR_REPOSITORY_LOGIN = 3; + + public final static int ERROR_REPOSITORY_NOT_FOUND = 4; + + public final static int OPERATION_CANCELLED = 8; + + public final static int REPOSITORY_COLLISION = 6; + + public final static int REPOSITORY_COMMENT_REQUIRED = 9; + + public final static int REPOSITORY_LOGGED_OUT = 10; + + public final static int ERROR_INTERNAL = 7; + + private String htmlMessage; + + protected String repositoryUrl; + + public RepositoryStatus(TaskRepository repository, int severity, String pluginId, int code, String message) { + this(repository.getRepositoryUrl(), severity, pluginId, code, message, null); + } + + public RepositoryStatus(TaskRepository repository, int severity, String pluginId, int code, String message, + Throwable e) { + this(repository.getRepositoryUrl(), severity, pluginId, code, message, e); + } + + public RepositoryStatus(String repositoryUrl, int severity, String pluginId, int code, String message) { + this(repositoryUrl, severity, pluginId, code, message, null); + } + + public RepositoryStatus(String repositoryUrl, int severity, String pluginId, int code, String message, Throwable e) { + super(severity, pluginId, code, message, e); + + if (repositoryUrl == null) { + throw new IllegalArgumentException("repositoryUrl must not be null"); //$NON-NLS-1$ + } + + this.repositoryUrl = repositoryUrl; + } + + /** + * Constructs a status object with a message. + */ + public RepositoryStatus(int severity, String pluginId, int code, String message) { + super(severity, pluginId, code, message, null); + } + + /** + * Constructs a status object with a message and an exception. that caused the error. + */ + public RepositoryStatus(int severity, String pluginId, int code, String message, Throwable e) { + super(severity, pluginId, code, message, e); + } + + /** + * Returns the message that is relevant to the code of this status. + */ + @Override + public String getMessage() { + String message = super.getMessage(); + if (message != null && !"".equals(message)) { //$NON-NLS-1$ + return message; + } + + Throwable exception = getException(); + if (exception != null) { + if (exception.getMessage() != null) { + return exception.getMessage(); + } + return exception.toString(); + } + + return ""; //$NON-NLS-1$ + } + + @Override + protected void setMessage(String message) { + super.setMessage((message != null) ? message : ""); //$NON-NLS-1$ + } + + protected void setHtmlMessage(String htmlMessage) { + this.htmlMessage = htmlMessage; + } + + public String getHtmlMessage() { + return htmlMessage; + } + + public boolean isHtmlMessage() { + return htmlMessage != null; + } + + public String getRepositoryUrl() { + return repositoryUrl; + } + + public static RepositoryStatus createInternalError(String pluginId, String message, Throwable t) { + return new RepositoryStatus(IStatus.ERROR, pluginId, RepositoryStatus.ERROR_INTERNAL, message, t); + } + + public static RepositoryStatus createHtmlStatus(int severity, String pluginId, int code, String message, + String htmlMessage) { + if (htmlMessage == null) { + throw new IllegalArgumentException("htmlMessage must not be null"); //$NON-NLS-1$ + } + + RepositoryStatus status = new RepositoryStatus(severity, pluginId, code, message); + status.setHtmlMessage(htmlMessage); + return status; + } + + public static RepositoryStatus createStatus(TaskRepository repository, int severity, String pluginId, String message) { + return createStatus(repository.getRepositoryUrl(), severity, pluginId, message); + } + + public static RepositoryStatus createStatus(String repositoryUrl, int severity, String pluginId, String message) { + return new RepositoryStatus(repositoryUrl, severity, pluginId, RepositoryStatus.ERROR_REPOSITORY, message); + } + + public static RepositoryStatus createLoginError(String repositoryUrl, String pluginId) { + return new RepositoryStatus(repositoryUrl, IStatus.ERROR, pluginId, RepositoryStatus.ERROR_REPOSITORY_LOGIN, + NLS.bind("Unable to login to {0}. Please validate credentials via Task Repositories view.", //$NON-NLS-1$ + repositoryUrl)); + } + + public static RepositoryStatus createNotFoundError(String repositoryUrl, String pluginId) { + return new RepositoryStatus(repositoryUrl, IStatus.ERROR, pluginId, + RepositoryStatus.ERROR_REPOSITORY_NOT_FOUND, NLS.bind("Repository {0} could not be found.", //$NON-NLS-1$ + repositoryUrl)); + } + + public static RepositoryStatus createCollisionError(String repositoryUrl, String pluginId) { + return new RepositoryStatus( + repositoryUrl, + IStatus.ERROR, + pluginId, + RepositoryStatus.REPOSITORY_COLLISION, + NLS.bind( + "Mid-air collision occurred while submitting to {0}.\n\nSynchronize task and re-submit changes.", //$NON-NLS-1$ + repositoryUrl)); + } + + public static RepositoryStatus createCommentRequiredError(String repositoryUrl, String pluginId) { + return new RepositoryStatus(repositoryUrl, IStatus.ERROR, pluginId, + RepositoryStatus.REPOSITORY_COMMENT_REQUIRED, + "You have to specify a new comment when making this change. Please comment on the reason for this change."); //$NON-NLS-1$ + } + + public static RepositoryStatus createHtmlStatus(String repositoryUrl, int severity, String pluginId, int code, + String message, String htmlMessage) { + if (htmlMessage == null) { + throw new IllegalArgumentException("htmlMessage must not be null"); //$NON-NLS-1$ + } + + RepositoryStatus status = new RepositoryStatus(repositoryUrl, severity, pluginId, code, message); + status.setHtmlMessage(htmlMessage); + return status; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryTemplate.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryTemplate.java new file mode 100644 index 000000000..412834ba1 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryTemplate.java @@ -0,0 +1,75 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Eugene Kuleshov and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Eugene Kuleshov - initial API and implementation + * Tasktop Technologies - improvements + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Specifies attributes for a task repository. + * + * @author Eugene Kuleshov + * @author Steffen Pingel + * @since 2.0 + */ +public final class RepositoryTemplate { + + public final Map<String, String> genericAttributes = new LinkedHashMap<String, String>(); + + public final String label; + + public final String repositoryUrl; + + public final String newTaskUrl; + + public final String taskPrefixUrl; + + public final String taskQueryUrl; + + public final String newAccountUrl; + + public final boolean anonymous; + + public final String version; + + public final boolean addAutomatically; + + public final String characterEncoding; + + public RepositoryTemplate(String label, String repositoryUrl, String characterEncoding, String version, + String newTaskUrl, String taskPrefix, String taskQuery, String newAccountUrl, boolean anonymous, + boolean addAutomatically) { + this.label = label; + this.repositoryUrl = repositoryUrl; + this.newTaskUrl = newTaskUrl; + this.taskPrefixUrl = taskPrefix; + this.taskQueryUrl = taskQuery; + this.newAccountUrl = newAccountUrl; + this.version = version; + this.anonymous = anonymous; + this.characterEncoding = characterEncoding; + this.addAutomatically = addAutomatically; + } + + public void addAttribute(String name, String value) { + genericAttributes.put(name, value); + } + + public String getAttribute(String name) { + return genericAttributes.get(name); + } + + public Map<String, String> getAttributes() { + return this.genericAttributes; + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskActivationAdapter.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskActivationAdapter.java new file mode 100644 index 000000000..127a66786 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskActivationAdapter.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +/** + * @author Rob Elves + * @since 3.0 + */ +public class TaskActivationAdapter implements ITaskActivationListener { + + public void preTaskActivated(ITask task) { + } + + public void preTaskDeactivated(ITask task) { + } + + public void taskActivated(ITask task) { + } + + public void taskDeactivated(ITask task) { + } + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskActivityAdapter.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskActivityAdapter.java new file mode 100644 index 000000000..43eba8a13 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskActivityAdapter.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +/** + * @author Steffen Pingel + * @since 3.0 + */ +public class TaskActivityAdapter implements ITaskActivityListener { + + public void activityReset() { + } + + public void elapsedTimeUpdated(ITask task, long newElapsedTime) { + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskMapping.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskMapping.java new file mode 100644 index 000000000..c2b38960a --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskMapping.java @@ -0,0 +1,219 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import java.util.Date; +import java.util.List; + +import org.eclipse.mylyn.tasks.core.ITask.PriorityLevel; +import org.eclipse.mylyn.tasks.core.data.TaskData; + +/** + * Clients may subclass. + * + * @author Steffen Pingel + * @since 3.0 + */ +public class TaskMapping implements ITaskMapping { + + /** + * @since 3.0 + */ + public void merge(ITaskMapping source) { + // ignore + } + + /** + * @since 3.0 + */ + public Date getCompletionDate() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public String getComponent() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public Date getCreationDate() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public String getDescription() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public Date getDueDate() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public Date getModificationDate() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public String getOwner() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public PriorityLevel getPriorityLevel() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public String getProduct() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public String getSummary() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public TaskData getTaskData() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public String getTaskKey() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public String getTaskKind() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public String getTaskUrl() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public List<String> getCc() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public List<String> getKeywords() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public String getReporter() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public String getResolution() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public String getTaskStatus() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public String getStatus() { + // ignore + return null; + } + + /** + * @since 3.0 + */ + public String getPriority() { + // ignore + return null; + } + + /** + * @since 3.2 + */ + public String getSeverity() { + // ignore + return null; + } + + /** + * @since 3.2 + */ + public String getVersion() { + // ignore + return null; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskRepository.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskRepository.java new file mode 100644 index 000000000..e9dc273ac --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskRepository.java @@ -0,0 +1,836 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + * Eugene Kuleshov - improvements + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.PlatformObject; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.commons.net.AuthenticationCredentials; +import org.eclipse.mylyn.commons.net.AuthenticationType; +import org.eclipse.mylyn.internal.tasks.core.IRepositoryConstants; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.internal.tasks.core.RepositoryPerson; + +/** + * Note that task repositories use Strings for storing time stamps because using Date objects led to the following + * problems: + * <ul> + * <li>Often we are unable to get the time zone of the repository so interpreting the date string correctly doesn't + * work.</li> + * <li>Even if we do know the time zone information the local clock may be wrong. This can cause lost incoming when + * asking the repository for all changes since date X.</li> + * <li>The solution we have come up with thus far is not to interpret the date as a DATE object but rather simply use + * the date string given to us by the repository itself.</li> + * </ul> + * + * @author Mik Kersten + * @author Rob Elves + * @author Eugene Kuleshov + * @author Steffen Pingel + * @since 2.0 + * @noextend This class is not intended to be subclassed by clients. + */ +@SuppressWarnings("deprecation") +public final class TaskRepository extends PlatformObject { + + public static final String DEFAULT_CHARACTER_ENCODING = "UTF-8"; //$NON-NLS-1$ + + private static final String USERNAME = ".username"; //$NON-NLS-1$ + + private static final String PASSWORD = ".password"; //$NON-NLS-1$ + + private static final String SAVE_PASSWORD = ".savePassword"; //$NON-NLS-1$ + + private static final String ENABLED = ".enabled"; //$NON-NLS-1$ + + private static final String AUTH_REPOSITORY = "org.eclipse.mylyn.tasklist.repositories"; //$NON-NLS-1$ + + // transient + private IStatus errorStatus = null; + + /** + * @deprecated use {@link #setCredentials(AuthenticationType, AuthenticationCredentials, boolean)} to access + * credentials + */ + @Deprecated + public static final String AUTH_PASSWORD = AUTH_REPOSITORY + PASSWORD; + + /** + * @deprecated use {@link #setCredentials(AuthenticationType, AuthenticationCredentials, boolean)} to access + * credentials + */ + @Deprecated + public static final String AUTH_USERNAME = AUTH_REPOSITORY + USERNAME; + + @Deprecated + public static final String ANONYMOUS_LOGIN = "org.eclipse.mylyn.tasklist.repositories.anonymous"; //$NON-NLS-1$ + + private static final String AUTH_HTTP = "org.eclipse.mylyn.tasklist.repositories.httpauth"; //$NON-NLS-1$ + + /** + * @deprecated use {@link #setCredentials(AuthenticationType, AuthenticationCredentials, boolean)} to access + * credentials + */ + @Deprecated + public static final String AUTH_HTTP_PASSWORD = AUTH_HTTP + PASSWORD; + + /** + * @deprecated use {@link #setCredentials(AuthenticationType, AuthenticationCredentials, boolean)} to access + * credentials + */ + @Deprecated + public static final String AUTH_HTTP_USERNAME = AUTH_HTTP + USERNAME; + + public static final String NO_VERSION_SPECIFIED = "unknown"; //$NON-NLS-1$ + + private static final String AUTH_SCHEME = "Basic"; //$NON-NLS-1$ + + private static final String AUTH_REALM = ""; //$NON-NLS-1$ + + private static final URL DEFAULT_URL; + + private static final String PROPERTY_CONFIG_TIMESTAMP = "org.eclipse.mylyn.tasklist.repositories.configuration.timestamp"; //$NON-NLS-1$ + + public static final String PROXY_USEDEFAULT = "org.eclipse.mylyn.tasklist.repositories.proxy.usedefault"; //$NON-NLS-1$ + + public static final String PROXY_HOSTNAME = "org.eclipse.mylyn.tasklist.repositories.proxy.hostname"; //$NON-NLS-1$ + + public static final String PROXY_PORT = "org.eclipse.mylyn.tasklist.repositories.proxy.port"; //$NON-NLS-1$ + + private static final String AUTH_PROXY = "org.eclipse.mylyn.tasklist.repositories.proxy"; //$NON-NLS-1$ + + /** + * @deprecated use {@link #setCredentials(AuthenticationType, AuthenticationCredentials, boolean)} to access + * credentials + */ + @Deprecated + public static final String PROXY_USERNAME = AUTH_PROXY + USERNAME; + + /** + * @deprecated use {@link #setCredentials(AuthenticationType, AuthenticationCredentials, boolean)} to access + * credentials + */ + @Deprecated + public static final String PROXY_PASSWORD = AUTH_PROXY + PASSWORD; + + public static final String OFFLINE = "org.eclipse.mylyn.tasklist.repositories.offline"; //$NON-NLS-1$ + + // HACK: Lock used to work around race condition in + // Platform.add/get/flushAuthorizationInfo() + private static final Object LOCK = new Object(); + + private final Set<PropertyChangeListener> propertyChangeListeners = new HashSet<PropertyChangeListener>(); + + // HACK: private credentials for headless operation + private static Map<String, Map<String, String>> credentials = new HashMap<String, Map<String, String>>(); + + static { + URL url = null; + try { + url = new URL("http://eclipse.org/mylyn"); //$NON-NLS-1$ + } catch (Exception ex) { + // TODO ? + } + DEFAULT_URL = url; + } + + private static String getKeyPrefix(AuthenticationType type) { + switch (type) { + case HTTP: + return AUTH_HTTP; + case PROXY: + return AUTH_PROXY; + case REPOSITORY: + return AUTH_REPOSITORY; + } + throw new IllegalArgumentException("Unknown authentication type: " + type); //$NON-NLS-1$ + } + + private boolean isCachedUserName; + + private String cachedUserName; + + private final Map<String, String> properties = new LinkedHashMap<String, String>(); + + /** + * Stores properties that are not persisted. Note that this map is currently cleared when flushCredentials() is + * invoked. + */ + private final Map<String, String> transientProperties = new HashMap<String, String>(); + + /* + * TODO: should be externalized and added to extension point, see bug 183606 + */ + private boolean isBugRepository = false; + + private transient volatile boolean updating; + + public TaskRepository(String connectorKind, String repositoryUrl) { + this(connectorKind, repositoryUrl, NO_VERSION_SPECIFIED); + } + + /** + * @deprecated use {@link #setProperty(String, String)} instead of passing a map + */ + @Deprecated + public TaskRepository(String kind, String serverUrl, Map<String, String> properties) { + setProperty(IRepositoryConstants.PROPERTY_CONNECTOR_KIND, kind); + setProperty(IRepositoryConstants.PROPERTY_URL, serverUrl); + this.properties.putAll(properties); + // use platform proxy by default (headless will need to set this to false) + this.setProperty(TaskRepository.PROXY_USEDEFAULT, new Boolean(true).toString()); + } + + /** + * for testing purposes sets repository time zone to local default time zone sets character encoding to + * DEFAULT_CHARACTER_ENCODING + */ + @Deprecated + public TaskRepository(String kind, String serverUrl, String version) { + this(kind, serverUrl, version, DEFAULT_CHARACTER_ENCODING, TimeZone.getDefault().getID()); + } + + @Deprecated + public TaskRepository(String connectorKind, String repositoryUrl, String version, String encoding, String timeZoneId) { + Assert.isNotNull(connectorKind); + Assert.isNotNull(repositoryUrl); + setProperty(IRepositoryConstants.PROPERTY_CONNECTOR_KIND, connectorKind); + setProperty(IRepositoryConstants.PROPERTY_URL, repositoryUrl); + setProperty(IRepositoryConstants.PROPERTY_VERSION, version); + setProperty(IRepositoryConstants.PROPERTY_ENCODING, encoding); + setProperty(IRepositoryConstants.PROPERTY_TIMEZONE, timeZoneId); + // use platform proxy by default (headless will need to set this to false) + this.setProperty(TaskRepository.PROXY_USEDEFAULT, new Boolean(true).toString()); + + // for backwards compatibility to versions prior to 2.2 + this.setProperty(AUTH_REPOSITORY + SAVE_PASSWORD, String.valueOf(true)); + this.setProperty(AUTH_HTTP + SAVE_PASSWORD, String.valueOf(true)); + this.setProperty(AUTH_PROXY + SAVE_PASSWORD, String.valueOf(true)); + } + + // TODO e3.4 move to new api + private void addAuthInfo(Map<String, String> map) { + synchronized (LOCK) { + try { + if (Platform.isRunning()) { + // write the map to the keyring + try { + Platform.addAuthorizationInfo(new URL(getRepositoryUrl()), AUTH_REALM, AUTH_SCHEME, map); + } catch (MalformedURLException ex) { + Platform.addAuthorizationInfo(DEFAULT_URL, getRepositoryUrl(), AUTH_SCHEME, map); + } + } else { + Map<String, String> headlessCreds = getAuthInfo(); + headlessCreds.putAll(map); + } + } catch (CoreException e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Could not set authorization credentials", e)); //$NON-NLS-1$ + } + } + } + + public void clearCredentials() { + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof TaskRepository) { + TaskRepository repository = (TaskRepository) object; + return getConnectorKind().equals(repository.getConnectorKind()) + && getRepositoryUrl().equals(repository.getRepositoryUrl()); + } + return false; + } + + // TODO e3.4 move to new api + public void flushAuthenticationCredentials() { + // legacy support for versions prior to 2.2 that did not set the enable flag + setProperty(getKeyPrefix(AuthenticationType.HTTP) + ENABLED, null); + setProperty(getKeyPrefix(AuthenticationType.PROXY) + ENABLED, null); + setProperty(getKeyPrefix(AuthenticationType.REPOSITORY) + ENABLED, null); + + synchronized (LOCK) { + isCachedUserName = false; + + transientProperties.clear(); + + try { + if (Platform.isRunning()) { + try { + Platform.flushAuthorizationInfo(new URL(getRepositoryUrl()), AUTH_REALM, AUTH_SCHEME); + } catch (MalformedURLException ex) { + Platform.flushAuthorizationInfo(DEFAULT_URL, getRepositoryUrl(), AUTH_SCHEME); + } + } else { + Map<String, String> headlessCreds = getAuthInfo(); + headlessCreds.clear(); + } + } catch (CoreException e) { + // FIXME propagate exception? + StatusHandler.fail(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Could not flush authorization credentials", e)); //$NON-NLS-1$ + } + } + } + + // TODO e3.4 move to new api + @SuppressWarnings( { "unchecked" }) + private Map<String, String> getAuthInfo() { + synchronized (LOCK) { + if (Platform.isRunning()) { + try { + return Platform.getAuthorizationInfo(new URL(getRepositoryUrl()), AUTH_REALM, AUTH_SCHEME); + } catch (MalformedURLException ex) { + return Platform.getAuthorizationInfo(DEFAULT_URL, getRepositoryUrl(), AUTH_SCHEME); + } catch (Exception e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, + "Could not retrieve authorization credentials", e)); //$NON-NLS-1$ + } + } else { + Map<String, String> headlessCreds = credentials.get(getRepositoryUrl()); + if (headlessCreds == null) { + headlessCreds = new HashMap<String, String>(); + credentials.put(getRepositoryUrl(), headlessCreds); + } + return headlessCreds; + } + return null; + } + } + + private String getAuthInfo(String property) { + Map<String, String> map = getAuthInfo(); + return map == null ? null : map.get(property); + } + + public String getCharacterEncoding() { + final String encoding = properties.get(IRepositoryConstants.PROPERTY_ENCODING); + return encoding == null || "".equals(encoding) ? DEFAULT_CHARACTER_ENCODING : encoding; //$NON-NLS-1$ + } + + /** + * Get the last refresh date as initialized {@link Date} object, null if not set<br /> + * + * @return {@link Date} configuration date, null if not set + */ + public Date getConfigurationDate() { + Date configDate = null; + String value = this.getProperty(PROPERTY_CONFIG_TIMESTAMP); + try { + configDate = new Date(Long.valueOf(value).longValue()); + + } catch (Exception e) { + + } + return configDate; + } + + /** + * @return "<unknown>" if kind is unknown + */ + public String getConnectorKind() { + String kind = properties.get(IRepositoryConstants.PROPERTY_CONNECTOR_KIND); + if (kind != null) { + return kind; + } else { + return IRepositoryConstants.KIND_UNKNOWN; + } + } + + /** + * Returns the credentials for an authentication type. + * + * @param authType + * the type of authentication + * @return null, if no credentials are set for <code>authType</code> + * @since 3.0 + */ + public synchronized AuthenticationCredentials getCredentials(AuthenticationType authType) { + String key = getKeyPrefix(authType); + + String enabled = getProperty(key + ENABLED); + if (enabled == null || "true".equals(enabled)) { //$NON-NLS-1$ + String userName = getAuthInfo(key + USERNAME); + String password; + + String savePassword = getProperty(key + SAVE_PASSWORD); + if (savePassword != null && "true".equals(savePassword)) { //$NON-NLS-1$ + password = getAuthInfo(key + PASSWORD); + } else { + password = transientProperties.get(key + PASSWORD); + } + + if (userName == null) { + userName = ""; //$NON-NLS-1$ + } + if (password == null) { + password = ""; //$NON-NLS-1$ + } + + if (enabled == null && userName.length() == 0) { + // API30: legacy support for versions prior to 2.2 that did not set the enable flag, remove for 3.0 + return null; + } + + return new AuthenticationCredentials(userName, password); + } else { + return null; + } + } + + /** + * @deprecated use {@link #getCredentials(AuthenticationType)} instead + */ + @Deprecated + public String getHttpPassword() { + return getPassword(AuthenticationType.HTTP); + } + + /** + * @deprecated use {@link #getCredentials(AuthenticationType)} instead + */ + @Deprecated + public String getHttpUser() { + return getUserName(AuthenticationType.HTTP); + } + + /** + * @deprecated use {@link #getCredentials(AuthenticationType)} instead + */ + @Deprecated + public String getPassword() { + return getPassword(AuthenticationType.REPOSITORY); + } + + /** + * Legacy support for < 2.2. Remove in 2.3. + */ + private String getPassword(AuthenticationType authType) { + AuthenticationCredentials credentials = getCredentials(authType); + return (credentials != null) ? credentials.getPassword() : null; + } + + public Map<String, String> getProperties() { + return new LinkedHashMap<String, String>(this.properties); + } + + public String getProperty(String name) { + return this.properties.get(name); + } + +// /** +// * @deprecated use {@link TaskRepositoryLocation#getProxyForHost(String, String)} instead +// */ +// @Deprecated +// public Proxy getProxy() { +// Proxy proxy = Proxy.NO_PROXY; +// if (isDefaultProxyEnabled()) { +// proxy = WebClientUtil.getPlatformProxy(getRepositoryUrl()); +// } else { +// +// String proxyHost = getProperty(PROXY_HOSTNAME); +// String proxyPort = getProperty(PROXY_PORT); +// String proxyUsername = ""; +// String proxyPassword = ""; +// if (proxyHost != null && proxyHost.length() > 0) { +// proxyUsername = getProxyUsername(); +// proxyPassword = getProxyPassword(); +// } +// proxy = WebClientUtil.getProxy(proxyHost, proxyPort, proxyUsername, proxyPassword); +// } +// return proxy; +// } + + /** + * @deprecated use {@link #getCredentials(AuthenticationType)} instead + */ + @Deprecated + public String getProxyPassword() { + return getPassword(AuthenticationType.PROXY); + } + + /** + * @deprecated use {@link #getCredentials(AuthenticationType)} instead + */ + @Deprecated + public String getProxyUsername() { + return getUserName(AuthenticationType.PROXY); + } + + /** + * @return the URL if the label property is not set + */ + public String getRepositoryLabel() { + String label = properties.get(IRepositoryConstants.PROPERTY_LABEL); + if (label != null && label.length() > 0) { + return label; + } else { + return getRepositoryUrl(); + } + } + + /** + * @since 3.0 + */ + public boolean getSavePassword(AuthenticationType authType) { + String value = getProperty(getKeyPrefix(authType) + SAVE_PASSWORD); + return value != null && "true".equals(value); //$NON-NLS-1$ + } + + public String getSynchronizationTimeStamp() { + return this.properties.get(IRepositoryConstants.PROPERTY_SYNCTIMESTAMP); + } + + public String getTimeZoneId() { + final String timeZoneId = properties.get(IRepositoryConstants.PROPERTY_TIMEZONE); + return timeZoneId == null || "".equals(timeZoneId) ? TimeZone.getDefault().getID() : timeZoneId; //$NON-NLS-1$ + } + + public String getUrl() { + return getRepositoryUrl(); + } + + /** + * @since 3.0 + */ + public String getRepositoryUrl() { + return properties.get(IRepositoryConstants.PROPERTY_URL); + } + + /** + * The username is cached since it needs to be retrieved frequently (e.g. for Task List decoration). + */ + public String getUserName() { + // NOTE: if anonymous, user name is "" string so we won't go to keyring + if (!isCachedUserName) { + cachedUserName = getUserName(AuthenticationType.REPOSITORY); + isCachedUserName = true; + } + return cachedUserName; + } + + /** + * Legacy support for < 2.2. Remove in 2.3. + */ + private String getUserName(AuthenticationType authType) { + AuthenticationCredentials credentials = getCredentials(authType); + return (credentials != null) ? credentials.getUserName() : null; + } + + public String getVersion() { + final String version = properties.get(IRepositoryConstants.PROPERTY_VERSION); + return version == null || "".equals(version) ? NO_VERSION_SPECIFIED : version; //$NON-NLS-1$ + } + + /** + * @deprecated use #getCredentials(AuthenticationType) instead + */ + @Deprecated + public boolean hasCredentials() { + String username = getUserName(); + String password = getPassword(); + return username != null && username.length() > 0 && password != null && password.length() > 0; + } + + @Override + public int hashCode() { + return getRepositoryUrl().hashCode() * 31 + getConnectorKind().hashCode(); + } + + public boolean hasProperty(String name) { + String value = getProperty(name); + return value != null && value.trim().length() > 0; + } + + /** + * @deprecated #getCredentials(AuthenticationType) == null instead + */ + @Deprecated + public boolean isAnonymous() { + return getProperty(ANONYMOUS_LOGIN) == null || "true".equals(getProperty(ANONYMOUS_LOGIN)); //$NON-NLS-1$ + } + + public boolean isBugRepository() { + return isBugRepository; + } + + /** + * Use platform proxy settings + */ + public boolean isDefaultProxyEnabled() { + return "true".equals(getProperty(PROXY_USEDEFAULT)); //$NON-NLS-1$ + } + + public boolean isOffline() { + return getProperty(OFFLINE) != null && "true".equals(getProperty(OFFLINE)); //$NON-NLS-1$ + } + + public void removeProperty(String key) { + this.properties.remove(key); + } + + /** + * @deprecated use {@link #setCredentials(AuthenticationType, AuthenticationCredentials, boolean)} instead + */ + @Deprecated + public void setAuthenticationCredentials(String username, String password) { + setCredentials(AuthenticationType.REPOSITORY, username, password); + } + + public void setBugRepository(boolean isBugRepository) { + this.isBugRepository = isBugRepository; + } + + public void setCharacterEncoding(String characterEncoding) { + properties.put(IRepositoryConstants.PROPERTY_ENCODING, characterEncoding == null ? DEFAULT_CHARACTER_ENCODING + : characterEncoding); + } + + /** + * Set the Configuration date to the {@link Date} indicated. + * + * @param configuration + * date {@link {@link Date} + */ + final public void setConfigurationDate(final Date date) { + this.setProperty(PROPERTY_CONFIG_TIMESTAMP, String.valueOf(date.getTime())); + // should persist here, but that can only be done by the TaskRepositoryManager + // However this is also included when persisting ordinary sync time + } + + /** + * Sets the credentials for <code>authType</code>. + * + * @param authType + * the type of authentication + * @param credentials + * the credentials, if null, the credentials for <code>authType</code> will be flushed + * @param savePassword + * if true, the password will be persisted in the platform key ring; otherwise it will be stored in + * memory only + * @since 3.0 + */ + public synchronized void setCredentials(AuthenticationType authType, AuthenticationCredentials credentials, + boolean savePassword) { + String key = getKeyPrefix(authType); + + setProperty(key + SAVE_PASSWORD, String.valueOf(savePassword)); + + if (credentials == null) { + setProperty(key + ENABLED, String.valueOf(false)); + transientProperties.remove(key + PASSWORD); + setCredentialsInternal("", "", key + USERNAME, key + PASSWORD); //$NON-NLS-1$ //$NON-NLS-2$ + } else { + setProperty(key + ENABLED, String.valueOf(true)); + if (savePassword) { + setCredentialsInternal(credentials.getUserName(), credentials.getPassword(), key + USERNAME, key + + PASSWORD); + transientProperties.remove(key + PASSWORD); + } else { + setCredentialsInternal(credentials.getUserName(), "", key + USERNAME, key + PASSWORD); //$NON-NLS-1$ + transientProperties.put(key + PASSWORD, credentials.getPassword()); + } + } + + if (authType == AuthenticationType.REPOSITORY) { + if (credentials == null) { + this.cachedUserName = null; + this.isCachedUserName = false; + } else { + this.cachedUserName = credentials.getUserName(); + this.isCachedUserName = true; + } + } + } + + /** + * Legacy support for < 2.2. Remove in 2.3. + */ + private void setCredentials(AuthenticationType type, String username, String password) { + if (username == null) { + setCredentials(type, null, true); + } else { + setCredentials(type, new AuthenticationCredentials(username, password), true); + } + + } + + private void setCredentialsInternal(String username, String password, String userProperty, String passwordProperty) { + Map<String, String> map = getAuthInfo(); + if (map == null) { + map = new HashMap<String, String>(); + } + + if (username != null) { + map.put(userProperty, username); + } + if (password != null) { + map.put(passwordProperty, password); + } + addAuthInfo(map); + } + + /** + * @deprecated use esetCredentials(AuthenticationType, AuthenticationCredentials, boolean) + */ + @Deprecated + public void setHttpAuthenticationCredentials(String username, String password) { + setCredentials(AuthenticationType.HTTP, username, password); + } + + public void setOffline(boolean offline) { + properties.put(OFFLINE, String.valueOf(offline)); + } + + /** + * @deprecated use {@link #setCredentials(AuthenticationType, AuthenticationCredentials, boolean)} instead + */ + @Deprecated + public void setProxyAuthenticationCredentials(String username, String password) { + setCredentials(AuthenticationType.PROXY, username, password); + } + + public void setRepositoryLabel(String repositoryLabel) { + setProperty(IRepositoryConstants.PROPERTY_LABEL, repositoryLabel); + } + + /** + * ONLY for use by IRepositoryConstants. To set the sync time call IRepositoryConstants.setSyncTime(repository, + * date); + */ + public void setSynchronizationTimeStamp(String syncTime) { + setProperty(IRepositoryConstants.PROPERTY_SYNCTIMESTAMP, syncTime); + } + + public void setProperty(String key, String newValue) { + String oldValue = this.properties.get(key); + if ((oldValue != null && !oldValue.equals(newValue)) || (oldValue == null && newValue != null)) { + this.properties.put(key, newValue); + notifyChangeListeners(key, oldValue, newValue); + } + } + + private void notifyChangeListeners(String key, String old, String value) { + PropertyChangeEvent event = new PropertyChangeEvent(this, key, old, value); + for (PropertyChangeListener listener : propertyChangeListeners) { + listener.propertyChange(event); + } + } + + public void setTimeZoneId(String timeZoneId) { + setProperty(IRepositoryConstants.PROPERTY_TIMEZONE, timeZoneId == null ? TimeZone.getDefault().getID() + : timeZoneId); + } + + /** + * @deprecated Use {@link #setRepositoryUrl(String)} instead + */ + @Deprecated + public void setUrl(String newUrl) { + setRepositoryUrl(newUrl); + } + + /** + * @since 3.0 + */ + public void setRepositoryUrl(String repositoryUrl) { + Assert.isNotNull(repositoryUrl); + properties.put(IRepositoryConstants.PROPERTY_URL, repositoryUrl); + } + + public void setVersion(String ver) { + properties.put(IRepositoryConstants.PROPERTY_VERSION, ver == null ? NO_VERSION_SPECIFIED : ver); + } + + @Override + public String toString() { + return getRepositoryUrl(); + } + + /** + * @since 3.0 + */ + public boolean isUpdating() { + return updating; + } + + /** + * @since 3.0 + */ + public void setUpdating(boolean updating) { + this.updating = updating; + } + + /** + * @since 3.0 + */ + public IRepositoryPerson createPerson(String personId) { + return new RepositoryPerson(this, personId); + } + + /** + * @since 3.0 + */ + public IStatus getStatus() { + return errorStatus; + } + + /** + * @since 3.0 + */ + public void setStatus(IStatus errorStatus) { + this.errorStatus = errorStatus; + } + + /** + * @since 3.0 + */ + public void addChangeListener(PropertyChangeListener listener) { + propertyChangeListeners.add(listener); + } + + /** + * @since 3.0 + */ + public void removeChangeListener(PropertyChangeListener listener) { + propertyChangeListeners.remove(listener); + } + + /** + * @since 3.1 + */ + public void setDefaultProxyEnabled(boolean useDefaultProxy) { + setProperty(TaskRepository.PROXY_USEDEFAULT, String.valueOf(useDefaultProxy)); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskRepositoryLocationFactory.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskRepositoryLocationFactory.java new file mode 100644 index 000000000..376b89838 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskRepositoryLocationFactory.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core; + +import org.eclipse.mylyn.commons.net.AbstractWebLocation; +import org.eclipse.mylyn.internal.tasks.core.TaskRepositoryLocation; + +/** + * @since 2.2 + * @author Steffen Pingel + */ +public class TaskRepositoryLocationFactory { + + /** + * @since 3.0 + */ + public AbstractWebLocation createWebLocation(final TaskRepository taskRepository) { + return new TaskRepositoryLocation(taskRepository); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskAttachmentHandler.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskAttachmentHandler.java new file mode 100644 index 000000000..5d3df4e90 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskAttachmentHandler.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import java.io.InputStream; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; + +/** + * Subclass to provide facility for uploading and downloading files from task repositories. + * + * @author Steffen Pingel + * @since 3.0 + */ +public abstract class AbstractTaskAttachmentHandler { + + public abstract boolean canGetContent(TaskRepository repository, ITask task); + + public abstract boolean canPostContent(TaskRepository repository, ITask task); + + public abstract InputStream getContent(TaskRepository repository, ITask task, TaskAttribute attachmentAttribute, + IProgressMonitor monitor) throws CoreException; + + public abstract void postContent(TaskRepository repository, ITask task, AbstractTaskAttachmentSource source, + String comment, TaskAttribute attachmentAttribute, IProgressMonitor monitor) throws CoreException; + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskAttachmentSource.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskAttachmentSource.java new file mode 100644 index 000000000..ae37c34db --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskAttachmentSource.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import java.io.InputStream; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * Clients may subclass. + * + * @author Steffen Pingel + * @since 3.0 + */ +public abstract class AbstractTaskAttachmentSource { + + public abstract InputStream createInputStream(IProgressMonitor monitor) throws CoreException; + + public abstract boolean isLocal(); + + public abstract long getLength(); + + public abstract String getName(); + + public abstract String getContentType(); + + public abstract String getDescription(); + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskDataHandler.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskDataHandler.java new file mode 100644 index 000000000..29cae91d2 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskDataHandler.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + * Frank Becker - improvements + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITaskMapping; +import org.eclipse.mylyn.tasks.core.RepositoryResponse; +import org.eclipse.mylyn.tasks.core.TaskRepository; + +/** + * Responsible for retrieving and posting task data to a repository. Clients may subclass. + * + * @author Mik Kersten + * @author Rob Elves + * @author Steffen Pingel + * @since 3.0 + */ +public abstract class AbstractTaskDataHandler { + + /** + * Download task data for each id provided + * + * Override getMultiTaskData() to return true and implement this method if connector supports download of multiple + * task data in one request. + * + * @since 3.0 + */ + public void getMultiTaskData(TaskRepository repository, Set<String> taskIds, TaskDataCollector collector, + IProgressMonitor monitor) throws CoreException { + throw new UnsupportedOperationException(); + } + + /** + * Return a reference to the newly created report in the case of new task submission, null otherwise + */ + public abstract RepositoryResponse postTaskData(TaskRepository repository, TaskData taskData, + Set<TaskAttribute> oldAttributes, IProgressMonitor monitor) throws CoreException; + + /** + * Initialize a new task data object with default attributes and values + */ + public abstract boolean initializeTaskData(TaskRepository repository, TaskData data, + ITaskMapping initializationData, IProgressMonitor monitor) throws CoreException; + + /** + * @since 2.2 + * @return false if this operation is not supported by the connector, true if initialized + */ + public boolean initializeSubTaskData(TaskRepository repository, TaskData taskData, TaskData parentTaskData, + IProgressMonitor monitor) throws CoreException { + return false; + } + + /** + * @param taskRepository + * TODO + * @param task + * the parent task, may be null + * @param task + * the parent task data, may be null + * @since 2.2 + */ + public boolean canInitializeSubTaskData(TaskRepository taskRepository, ITask task) { + return false; + } + + public abstract TaskAttributeMapper getAttributeMapper(TaskRepository taskRepository); + + /** + * @param taskRepository + * TODO + * @return true if connector support downloading multiple task data in single request, false otherwise. If true, + * override and implement getMultiTaskData + */ + public boolean canGetMultiTaskData(TaskRepository taskRepository) { + return false; + } + + public void migrateTaskData(TaskRepository taskRepository, TaskData taskData) { + } + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/ITaskDataManager.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/ITaskDataManager.java new file mode 100644 index 000000000..5aece1f75 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/ITaskDataManager.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ITaskDataManager { + + /** + * @since 3.0 + */ + public ITaskDataWorkingCopy createWorkingCopy(ITask task, TaskData taskData); + + /** + * @since 3.0 + */ + public abstract ITaskDataWorkingCopy getWorkingCopy(ITask task) throws CoreException; + + /** + * @since 3.0 + */ + public abstract void discardEdits(ITask task) throws CoreException; + + /** + * @since 3.0 + */ + public abstract TaskData getTaskData(ITask task) throws CoreException; + + /** + * @since 3.0 + */ + public abstract TaskData getTaskData(TaskRepository task, String taskId) throws CoreException; + + /** + * @since 3.0 + */ + public abstract boolean hasTaskData(ITask task); + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/ITaskDataWorkingCopy.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/ITaskDataWorkingCopy.java new file mode 100644 index 000000000..1ff63b359 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/ITaskDataWorkingCopy.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ITaskDataWorkingCopy { + + /** + * @since 3.0 + */ + public abstract TaskData getEditsData(); + + /** + * @since 3.0 + */ + public abstract TaskData getLastReadData(); + + /** + * @since 3.0 + */ + public abstract TaskData getLocalData(); + + /** + * @since 3.0 + */ + public abstract TaskData getRepositoryData(); + + /** + * @since 3.0 + */ + public abstract boolean isSaved(); + + /** + * @since 3.0 + */ + public abstract void revert(); + + /** + * @since 3.0 + */ + public abstract void refresh(IProgressMonitor monitor) throws CoreException; + + /** + * @since 3.0 + */ + public abstract void save(Set<TaskAttribute> edits, IProgressMonitor monitor) throws CoreException; + + /** + * @since 3.0 + */ + public abstract String getConnectorKind(); + + /** + * @since 3.0 + */ + public abstract String getRepositoryUrl(); + + /** + * @since 3.0 + */ + public abstract String getTaskId(); + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentMapper.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentMapper.java new file mode 100644 index 000000000..4007e61ff --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentMapper.java @@ -0,0 +1,298 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import java.util.Date; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.mylyn.tasks.core.IRepositoryPerson; +import org.eclipse.mylyn.tasks.core.ITaskAttachment; + +/** + * @since 3.0 + * @author Steffen Pingel + */ +public class TaskAttachmentMapper { + + private IRepositoryPerson author; + + private String comment; + + private String contentType; + + private Date creationDate; + + private Boolean deprecated; + + private String description; + + private String fileName; + + private Long length; + + private Boolean patch; + + private String url; + + private String attachmentId; + + public TaskAttachmentMapper() { + } + + public String getAttachmentId() { + return attachmentId; + } + + public IRepositoryPerson getAuthor() { + return author; + } + + public String getComment() { + return comment; + } + + public String getContentType() { + return contentType; + } + + public Date getCreationDate() { + return creationDate; + } + + public String getDescription() { + return description; + } + + public String getFileName() { + return fileName; + } + + public Long getLength() { + return length; + } + + public String getUrl() { + return url; + } + + public Boolean isDeprecated() { + return deprecated; + } + + public Boolean isPatch() { + return patch; + } + + public void setAttachmentId(String attachmentId) { + this.attachmentId = attachmentId; + } + + public void setAuthor(IRepositoryPerson author) { + this.author = author; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public void setDeprecated(Boolean deprecated) { + this.deprecated = deprecated; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public void setLength(Long length) { + this.length = length; + } + + public void setPatch(Boolean patch) { + this.patch = patch; + } + + public void setUrl(String url) { + this.url = url; + } + + public static TaskAttachmentMapper createFrom(TaskAttribute taskAttribute) { + Assert.isNotNull(taskAttribute); + TaskAttributeMapper mapper = taskAttribute.getTaskData().getAttributeMapper(); + TaskAttachmentMapper attachment = new TaskAttachmentMapper(); + attachment.setAttachmentId(mapper.getValue(taskAttribute)); + TaskAttribute child = taskAttribute.getMappedAttribute(TaskAttribute.ATTACHMENT_AUTHOR); + if (child != null) { + attachment.setAuthor(mapper.getRepositoryPerson(child)); + } + child = taskAttribute.getMappedAttribute(TaskAttribute.ATTACHMENT_CONTENT_TYPE); + if (child != null) { + attachment.setContentType(mapper.getValue(child)); + } + child = taskAttribute.getMappedAttribute(TaskAttribute.ATTACHMENT_DATE); + if (child != null) { + attachment.setCreationDate(mapper.getDateValue(child)); + } + child = taskAttribute.getMappedAttribute(TaskAttribute.ATTACHMENT_DESCRIPTION); + if (child != null) { + attachment.setDescription(mapper.getValue(child)); + } + child = taskAttribute.getMappedAttribute(TaskAttribute.ATTACHMENT_FILENAME); + if (child != null) { + attachment.setFileName(mapper.getValue(child)); + } + child = taskAttribute.getMappedAttribute(TaskAttribute.ATTACHMENT_IS_DEPRECATED); + if (child != null) { + attachment.setDeprecated(mapper.getBooleanValue(child)); + } + child = taskAttribute.getMappedAttribute(TaskAttribute.ATTACHMENT_IS_PATCH); + if (child != null) { + attachment.setPatch(mapper.getBooleanValue(child)); + } + child = taskAttribute.getMappedAttribute(TaskAttribute.ATTACHMENT_SIZE); + if (child != null) { + Long value = mapper.getLongValue(child); + if (value != null) { + attachment.setLength(value); + } + } + child = taskAttribute.getMappedAttribute(TaskAttribute.ATTACHMENT_URL); + if (child != null) { + attachment.setUrl(mapper.getValue(child)); + } + return attachment; + } + + public void applyTo(TaskAttribute taskAttribute) { + Assert.isNotNull(taskAttribute); + TaskData taskData = taskAttribute.getTaskData(); + TaskAttributeMapper mapper = taskData.getAttributeMapper(); + taskAttribute.getMetaData().defaults().setType(TaskAttribute.TYPE_ATTACHMENT); + if (getAttachmentId() != null) { + mapper.setValue(taskAttribute, getAttachmentId()); + } + if (getAuthor() != null) { + TaskAttribute child = taskAttribute.createMappedAttribute(TaskAttribute.ATTACHMENT_AUTHOR); + child.getMetaData().defaults().setType(TaskAttribute.TYPE_PERSON); + mapper.setRepositoryPerson(child, getAuthor()); + } + if (getContentType() != null) { + TaskAttribute child = taskAttribute.createMappedAttribute(TaskAttribute.ATTACHMENT_CONTENT_TYPE); + child.getMetaData().defaults().setType(TaskAttribute.TYPE_SHORT_TEXT); + mapper.setValue(child, getContentType()); + } + if (getCreationDate() != null) { + TaskAttribute child = taskAttribute.createMappedAttribute(TaskAttribute.ATTACHMENT_DATE); + child.getMetaData().defaults().setType(TaskAttribute.TYPE_DATE); + mapper.setDateValue(child, getCreationDate()); + } + if (getDescription() != null) { + TaskAttribute child = taskAttribute.createMappedAttribute(TaskAttribute.ATTACHMENT_DESCRIPTION); + child.getMetaData().defaults().setType(TaskAttribute.TYPE_SHORT_TEXT); + mapper.setValue(child, getDescription()); + } + if (getFileName() != null) { + TaskAttribute child = taskAttribute.createMappedAttribute(TaskAttribute.ATTACHMENT_FILENAME); + child.getMetaData().defaults().setType(TaskAttribute.TYPE_SHORT_TEXT); + mapper.setValue(child, getFileName()); + } + if (isDeprecated() != null) { + TaskAttribute child = taskAttribute.createMappedAttribute(TaskAttribute.ATTACHMENT_IS_DEPRECATED); + child.getMetaData().defaults().setType(TaskAttribute.TYPE_BOOLEAN); + mapper.setBooleanValue(child, isDeprecated()); + } + if (isPatch() != null) { + TaskAttribute child = taskAttribute.createMappedAttribute(TaskAttribute.ATTACHMENT_IS_PATCH); + child.getMetaData().defaults().setType(TaskAttribute.TYPE_BOOLEAN); + mapper.setBooleanValue(child, isPatch()); + } + if (getLength() != null) { + TaskAttribute child = taskAttribute.createMappedAttribute(TaskAttribute.ATTACHMENT_SIZE); + mapper.setLongValue(child, getLength()); + } + if (getUrl() != null) { + TaskAttribute child = taskAttribute.createMappedAttribute(TaskAttribute.ATTACHMENT_URL); + child.getMetaData().defaults().setType(TaskAttribute.TYPE_URL); + mapper.setValue(child, getUrl()); + } + } + + public void applyTo(ITaskAttachment taskAttachment) { + Assert.isNotNull(taskAttachment); + if (getAuthor() != null) { + taskAttachment.setAuthor(getAuthor()); + } + if (getContentType() != null) { + taskAttachment.setContentType(getContentType()); + } + if (getCreationDate() != null) { + taskAttachment.setCreationDate(getCreationDate()); + } + if (getDescription() != null) { + taskAttachment.setDescription(getDescription()); + } + if (getFileName() != null) { + taskAttachment.setFileName(getFileName()); + } + if (isDeprecated() != null) { + taskAttachment.setDeprecated(isDeprecated()); + } + if (isPatch() != null) { + taskAttachment.setPatch(isPatch()); + } + if (getLength() != null) { + taskAttachment.setLength(getLength()); + } + if (url != null) { + taskAttachment.setUrl(getUrl()); + } + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TaskAttachmentMapper)) { + return false; + } + TaskAttachmentMapper other = (TaskAttachmentMapper) obj; + if ((other.attachmentId != null && this.attachmentId != null) && !other.attachmentId.equals(this.attachmentId)) { + return false; + } + if ((other.deprecated != null && this.deprecated != null) && !(other.deprecated == this.deprecated)) { + return false; + } + if ((other.patch != null && this.patch != null) && !(other.patch == this.patch)) { + return false; + } + if ((other.description != null && this.description != null) && !other.description.equals(this.description)) { + return false; + } + if ((other.contentType != null && this.contentType != null) && !other.contentType.equals(this.contentType)) { + return false; + } + if ((other.fileName != null && this.fileName != null) && !other.fileName.equals(this.fileName)) { + return false; + } + return true; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentModel.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentModel.java new file mode 100644 index 000000000..933dd9ff4 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentModel.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; + +/** + * @author Steffen Pingel + * @since 3.0 + */ +public class TaskAttachmentModel { + + private boolean attachContext; + + private final TaskAttribute attribute; + + private String comment; + + private AbstractTaskAttachmentSource source; + + private final ITask task; + + private final TaskRepository taskRepository; + + private String contentType; + + public TaskAttachmentModel(TaskRepository taskRepository, ITask task, TaskAttribute attribute) { + this.taskRepository = taskRepository; + this.task = task; + this.attribute = attribute; + } + + public boolean getAttachContext() { + return attachContext; + } + + public TaskAttribute getAttribute() { + return attribute; + } + + public String getComment() { + return comment; + } + + public AbstractTaskAttachmentSource getSource() { + return source; + } + + public ITask getTask() { + return task; + } + + public TaskRepository getTaskRepository() { + return taskRepository; + } + + public void setAttachContext(boolean attachContext) { + this.attachContext = attachContext; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public void setSource(AbstractTaskAttachmentSource source) { + this.source = source; + } + + public String getContentType() { + if (contentType == null && getSource() != null) { + return getSource().getContentType(); + } + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentPartSource.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentPartSource.java new file mode 100644 index 000000000..97287f635 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentPartSource.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.commons.httpclient.methods.multipart.PartSource; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; + +/** + * @since 3.1 + * @author Steffen Pingel + */ +public class TaskAttachmentPartSource implements PartSource { + + private final AbstractTaskAttachmentSource attachment; + + private final String filename; + + public TaskAttachmentPartSource(AbstractTaskAttachmentSource attachment, String filename) { + this.attachment = attachment; + this.filename = filename; + } + + public InputStream createInputStream() throws IOException { + try { + return attachment.createInputStream(null); + } catch (CoreException e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Error attaching file", e)); //$NON-NLS-1$ + throw new IOException("Failed to create source stream"); //$NON-NLS-1$ + } + } + + public String getFileName() { + return filename; + } + + public long getLength() { + return attachment.getLength(); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttribute.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttribute.java new file mode 100644 index 000000000..d6b3e0027 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttribute.java @@ -0,0 +1,563 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; + +/** + * Encapsulates attributes for task data. + * + * @author Rob Elves + * @author Steffen Pingel + * @since 3.0 + * @noextend This class is not intended to be subclassed by clients. + */ +public final class TaskAttribute { + + /** + * Boolean attribute. If true, repository user needs to be added to the cc list. + */ + public static final String ADD_SELF_CC = "task.common.addselfcc"; //$NON-NLS-1$ + + public static final String ATTACHMENT_AUTHOR = "task.common.attachment.author"; //$NON-NLS-1$ + + public static final String ATTACHMENT_CONTENT_TYPE = "task.common.attachment.ctype"; //$NON-NLS-1$ + + public static final String ATTACHMENT_DATE = "task.common.attachment.date"; //$NON-NLS-1$ + + public static final String ATTACHMENT_DESCRIPTION = "task.common.attachment.description"; //$NON-NLS-1$ + + public static final String ATTACHMENT_FILENAME = "filename"; //$NON-NLS-1$ + + public static final String ATTACHMENT_ID = "task.common.attachment.id"; //$NON-NLS-1$ + + public static final String ATTACHMENT_IS_DEPRECATED = "task.common.attachment.deprecated"; //$NON-NLS-1$ + + public static final String ATTACHMENT_IS_PATCH = "task.common.attachment.patch"; //$NON-NLS-1$ + + public static final String ATTACHMENT_SIZE = "task.common.attachment.size"; //$NON-NLS-1$ + + public static final String ATTACHMENT_URL = "task.common.attachment.url"; //$NON-NLS-1$ + + public static final String COMMENT_ATTACHMENT_ID = "task.common.comment.attachment.id"; //$NON-NLS-1$ + + public static final String COMMENT_AUTHOR = "task.common.comment.author"; //$NON-NLS-1$ + + @Deprecated + public static final String COMMENT_AUTHOR_NAME = "task.common.comment.author.name"; //$NON-NLS-1$ + + public static final String COMMENT_DATE = "task.common.comment.date"; //$NON-NLS-1$ + + public static final String COMMENT_HAS_ATTACHMENT = "task.common.comment.attachment"; //$NON-NLS-1$ + + public static final String COMMENT_NEW = "task.common.comment.new"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String COMMENT_NUMBER = "task.common.comment.number"; //$NON-NLS-1$ + + public static final String COMMENT_TEXT = "task.common.comment.text"; //$NON-NLS-1$ + + public static final String COMMENT_URL = "task.common.comment.url"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String COMPONENT = "task.common.component"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String DATE_COMPLETION = "task.common.date.completed"; //$NON-NLS-1$ + + public static final String DATE_CREATION = "task.common.date.created"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String DATE_DUE = "task.common.date.due"; //$NON-NLS-1$ + + public static final String DATE_MODIFICATION = "task.common.date.modified"; //$NON-NLS-1$ + + public static final String DESCRIPTION = "task.common.description"; //$NON-NLS-1$ + + public static final String KEYWORDS = "task.common.keywords"; //$NON-NLS-1$ + + public static final String KIND_DEFAULT = "task.common.kind.default"; //$NON-NLS-1$ + + public static final String KIND_OPERATION = "task.common.kind.operation"; //$NON-NLS-1$ + + public static final String KIND_PEOPLE = "task.common.kind.people"; //$NON-NLS-1$ + + //public static final String META_SHOW_IN_ATTRIBUTES_SECTION = "task.meta.showInTaskEditorAttributesSection"; + + public static final String META_ASSOCIATED_ATTRIBUTE_ID = "task.meta.associated.attribute"; //$NON-NLS-1$ + + public static final String META_ATTRIBUTE_KIND = "task.meta.attributeKind"; //$NON-NLS-1$ + + public static final String META_ATTRIBUTE_TYPE = "task.meta.type"; //$NON-NLS-1$ + + public static final String META_DEFAULT_OPTION = "task.meta.defaultOption"; //$NON-NLS-1$ + +// public static final String META_DETAIL_LEVEL = "task.meta.detailLevel"; + + public static final String META_LABEL = "task.meta.label"; //$NON-NLS-1$ + + public static final String META_READ_ONLY = "task.meta.readOnly"; //$NON-NLS-1$ + + public static final String NEW_ATTACHMENT = "task.common.new.attachment"; //$NON-NLS-1$ + + // XXX merge with USER_CC + //public static final String NEW_CC = "task.common.newcc"; + + public static final String OPERATION = "task.common.operation"; //$NON-NLS-1$ + + public static final String PERSON_NAME = "task.common.person.name"; //$NON-NLS-1$ + + public static final String PREFIX_ATTACHMENT = "task.common.attachment-"; //$NON-NLS-1$ + + public static final String PREFIX_COMMENT = "task.common.comment-"; //$NON-NLS-1$ + + // XXX merge with USER_CC + //public static final String REMOVE_CC = "task.common.removecc"; + + public static final String PREFIX_OPERATION = "task.common.operation-"; //$NON-NLS-1$ + + public static final String PRIORITY = "task.common.priority"; //$NON-NLS-1$ + + public static final String PRODUCT = "task.common.product"; //$NON-NLS-1$ + + public static final String RESOLUTION = "task.common.resolution"; //$NON-NLS-1$ + + public static final String STATUS = "task.common.status"; //$NON-NLS-1$ + + public static final String SUMMARY = "task.common.summary"; //$NON-NLS-1$ + + public static final String TASK_KEY = "task.common.key"; //$NON-NLS-1$ + + public static final String TASK_KIND = "task.common.kind"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TASK_URL = "task.common.url"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_ATTACHMENT = "attachment"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_BOOLEAN = "boolean"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_COMMENT = "comment"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_CONTAINER = "container"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_DATE = "date"; //$NON-NLS-1$ + + /** + * @since 3.1 + */ + public static final String TYPE_DATETIME = "dateTime"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_INTEGER = "integer"; //$NON-NLS-1$ + + /** + * @since 3.1 + */ + public static final String TYPE_LONG = "long"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_LONG_RICH_TEXT = "longRichText"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_LONG_TEXT = "longText"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_MULTI_SELECT = "multiSelect"; //$NON-NLS-1$ + + public static final String TYPE_OPERATION = "operation"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_PERSON = "person"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_SHORT_RICH_TEXT = "shortRichText"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_SHORT_TEXT = "shortText"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_SINGLE_SELECT = "singleSelect"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_TASK_DEPENDENCY = "taskDepenedency"; //$NON-NLS-1$ + + public static final String TYPE_URL = "url"; //$NON-NLS-1$ + + public static final String USER_ASSIGNED = "task.common.user.assigned"; //$NON-NLS-1$ + + @Deprecated + public static final String USER_ASSIGNED_NAME = "task.common.user.assigned.name"; //$NON-NLS-1$ + + public static final String USER_CC = "task.common.user.cc"; //$NON-NLS-1$ + + public static final String USER_REPORTER = "task.common.user.reporter"; //$NON-NLS-1$ + + @Deprecated + public static final String USER_REPORTER_NAME = "task.common.user.reporter.name"; //$NON-NLS-1$ + + /** + * @since 3.2 + */ + public static final String SEVERITY = "task.common.severity"; //$NON-NLS-1$ + + /** + * @since 3.2 + */ + public static final String VERSION = "task.common.version"; //$NON-NLS-1$ + + private Map<String, TaskAttribute> attributeById; + + private final String attributeId; + + private Map<String, String> metaData; + + private Map<String, String> optionByKey; + + private final TaskAttribute parentAttribute; + + private final TaskData taskData; + + /** + * Attribute's values (selected or added) + */ + private final List<String> values; + + public TaskAttribute(TaskAttribute parentAttribute, String attributeId) { + Assert.isNotNull(parentAttribute); + Assert.isNotNull(attributeId); + this.parentAttribute = parentAttribute; + this.attributeId = attributeId; + this.taskData = parentAttribute.getTaskData(); + this.values = new ArrayList<String>(1); + parentAttribute.add(this); + } + + /** + * Constructor for the root node. + */ + TaskAttribute(TaskData taskData) { + Assert.isNotNull(taskData); + this.parentAttribute = null; + this.taskData = taskData; + this.attributeId = "root"; //$NON-NLS-1$ + this.values = new ArrayList<String>(1); + } + + private void add(TaskAttribute attribute) { + if (attributeById == null) { + attributeById = new LinkedHashMap<String, TaskAttribute>(); + } + attributeById.put(attribute.getId(), attribute); + } + + public void addValue(String value) { + Assert.isNotNull(value); + values.add(value); + } + + public void clearAttributes() { + attributeById = null; + } + + void clearMetaDataMap() { + metaData = null; + } + + public void clearOptions() { + optionByKey = null; + } + + public void clearValues() { + values.clear(); + } + + public TaskAttribute createAttribute(String attributeId) { + return new TaskAttribute(this, attributeId); + } + + public void deepAddCopy(TaskAttribute source) { + TaskAttribute target = createAttribute(source.getId()); + target.values.addAll(source.values); + if (source.metaData != null) { + target.metaData = new LinkedHashMap<String, String>(source.metaData); + } + if (source.optionByKey != null) { + target.optionByKey = new LinkedHashMap<String, String>(source.optionByKey); + } + if (source.attributeById != null) { + for (TaskAttribute child : source.attributeById.values()) { + target.deepAddCopy(child); + } + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final TaskAttribute other = (TaskAttribute) obj; + if (attributeId == null) { + if (other.attributeId != null) { + return false; + } + } else if (!attributeId.equals(other.attributeId)) { + return false; + } + return true; + } + + public TaskAttribute getAttribute(String attributeId) { + Assert.isNotNull(attributeId); + return (attributeById != null) ? attributeById.get(attributeId) : null; + } + + public Map<String, TaskAttribute> getAttributes() { + if (attributeById != null) { + return Collections.unmodifiableMap(attributeById); + } else { + return Collections.emptyMap(); + } + } + + public String getId() { + return attributeId; + } + + public TaskAttribute getMappedAttribute(String attributeId) { + Assert.isNotNull(attributeId); + return (attributeById != null) ? attributeById.get(getTaskData().getAttributeMapper().mapToRepositoryKey(this, + attributeId)) : null; + } + + public TaskAttribute getMappedAttribute(String[] path) { + TaskAttribute attribute = this; + for (String id : path) { + attribute = attribute.getMappedAttribute(id); + if (attribute == null) { + break; + } + } + return attribute; + } + + String getMetaDatum(String key) { + return (metaData != null) ? metaData.get(key) : null; + } + + Map<String, String> getMetaDataMap() { + if (metaData != null) { + return Collections.unmodifiableMap(metaData); + } else { + return Collections.emptyMap(); + } + } + + public String getOption(String key) { + return (optionByKey != null) ? optionByKey.get(key) : null; + } + + public Map<String, String> getOptions() { + if (optionByKey != null) { + return Collections.unmodifiableMap(optionByKey); + } else { + return Collections.emptyMap(); + } + } + + public TaskAttribute getParentAttribute() { + return parentAttribute; + } + + public String[] getPath() { + List<String> path = new ArrayList<String>(); + TaskAttribute attribute = this; + while (attribute.getParentAttribute() != null) { + path.add(attribute.getId()); + attribute = attribute.getParentAttribute(); + } + Collections.reverse(path); + return path.toArray(new String[0]); + } + + public TaskAttributeMetaData getMetaData() { + return new TaskAttributeMetaData(this); + } + + public TaskData getTaskData() { + return taskData; + } + + public String getValue() { + if (values.size() > 0) { + return values.get(0); + } else { + return ""; //$NON-NLS-1$ + } + } + + public List<String> getValues() { + return Collections.unmodifiableList(values); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((attributeId == null) ? 0 : attributeId.hashCode()); + return result; + } + + void putMetaDatum(String key, String value) { + Assert.isNotNull(key); + Assert.isNotNull(value); + if (metaData == null) { + metaData = new LinkedHashMap<String, String>(); + } + metaData.put(key, value); + } + + /** + * Adds an attribute option value + * + * @param readableValue + * The value displayed on the screen + * @param parameterValue + * The option value used when sending the form to the server + */ + public void putOption(String key, String value) { + Assert.isNotNull(key); + Assert.isNotNull(value); + if (optionByKey == null) { + optionByKey = new LinkedHashMap<String, String>(); + } + optionByKey.put(key, value); + } + + public void removeAttribute(String attributeId) { + if (attributeById != null) { + attributeById.remove(attributeId); + } + } + + void removeMetaDatum(String metaDataId) { + if (metaData != null) { + metaData.remove(metaDataId); + } + } + + public void removeValue(String value) { + values.remove(value); + } + + public void setValue(String value) { + Assert.isNotNull(value); + if (values.size() > 0) { + values.clear(); + } + values.add(value); + } + + public void setValues(List<String> values) { + this.values.clear(); + this.values.addAll(values); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + toString(sb, ""); //$NON-NLS-1$ + return sb.toString(); + } + + private void toString(StringBuilder sb, String prefix) { + sb.append(prefix); + sb.append("TaskAttribute[id="); //$NON-NLS-1$ + sb.append(attributeId); + sb.append(",values="); //$NON-NLS-1$ + sb.append(values); + sb.append(",options="); //$NON-NLS-1$ + sb.append(optionByKey); + sb.append(",metaData="); //$NON-NLS-1$ + sb.append(metaData); + sb.append("]"); //$NON-NLS-1$ + if (attributeById != null) { + for (TaskAttribute child : attributeById.values()) { + sb.append("\n"); //$NON-NLS-1$ + child.toString(sb, prefix + " "); //$NON-NLS-1$ + } + } + } + + public TaskAttribute createMappedAttribute(String attributeId) { + Assert.isNotNull(attributeId); + String mappedAttributeId = getTaskData().getAttributeMapper().mapToRepositoryKey(this, attributeId); + Assert.isNotNull(mappedAttributeId); + return new TaskAttribute(this, mappedAttributeId); + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttributeMapper.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttributeMapper.java new file mode 100644 index 000000000..98e5c31c9 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttributeMapper.java @@ -0,0 +1,276 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.mylyn.tasks.core.IRepositoryPerson; +import org.eclipse.mylyn.tasks.core.ITaskAttachment; +import org.eclipse.mylyn.tasks.core.ITaskComment; +import org.eclipse.mylyn.tasks.core.TaskRepository; + +/** + * @author Steffen Pingel + * @since 3.0 + */ +public class TaskAttributeMapper { + + private final TaskRepository taskRepository; + + public TaskAttributeMapper(TaskRepository taskRepository) { + Assert.isNotNull(taskRepository); + this.taskRepository = taskRepository; + } + + public TaskAttribute createTaskAttachment(TaskData taskData) { + TaskAttribute taskAttribute = taskData.getRoot().createAttribute( + mapToRepositoryKey(taskData.getRoot(), TaskAttribute.NEW_ATTACHMENT)); +// TaskAttachmentMapper mapper = TaskAttachmentMapper.createFrom(taskAttribute); +// mapper.setContentType(""); +// mapper.setFileName(""); +// mapper.setContentType(""); +// mapper.applyTo(taskAttribute); + return taskAttribute; + } + + public boolean equals(TaskAttribute newAttribute, TaskAttribute oldAttribute) { + return newAttribute.getValues().equals(oldAttribute.getValues()); + } + + public TaskAttribute getAssoctiatedAttribute(TaskAttribute taskAttribute) { + String id = taskAttribute.getMetaDatum(TaskAttribute.META_ASSOCIATED_ATTRIBUTE_ID); + if (id != null) { + // look up as nested attribute first + TaskAttribute associatedAttribute = taskAttribute.getAttribute(id); + if (associatedAttribute != null) { + return associatedAttribute; + } + // fall back to root + return taskAttribute.getTaskData().getRoot().getAttribute(id); + } + return null; + } + + public TaskAttribute getAssoctiatedAttribute(TaskOperation taskOperation) { + TaskAttribute taskAttribute = taskOperation.getTaskAttribute(); + if (taskAttribute != null) { + return getAssoctiatedAttribute(taskAttribute); + } + return null; + } + + public List<TaskAttribute> getAttributesByType(TaskData taskData, String type) { + Assert.isNotNull(taskData); + Assert.isNotNull(type); + List<TaskAttribute> result = new ArrayList<TaskAttribute>(); + for (TaskAttribute taskAttribute : taskData.getRoot().getAttributes().values()) { + if (type.equals(taskAttribute.getMetaData().getType())) { + result.add(taskAttribute); + } + } + return result; + } + + public boolean getBooleanValue(TaskAttribute attribute) { + String booleanString = attribute.getValue(); + if (booleanString != null && booleanString.length() > 0) { + return Boolean.parseBoolean(booleanString); + } + return false; + } + + public Date getDateValue(TaskAttribute attribute) { + String dateString = attribute.getValue(); + try { + if (dateString != null && dateString.length() > 0) { + return new Date(Long.parseLong(dateString)); + } + } catch (NumberFormatException e) { + // ignore + } + return null; + } + + public String getDefaultOption(TaskAttribute taskAttribute) { + return taskAttribute.getMetaData().getDefaultOption(); + } + + public Integer getIntegerValue(TaskAttribute attribute) { + String integerString = attribute.getValue(); + try { + if (integerString != null) { + return Integer.parseInt(integerString); + } + } catch (NumberFormatException e) { + // ignore + } + return null; + } + + public String getLabel(TaskAttribute taskAttribute) { + return taskAttribute.getMetaData().getLabel(); + } + + public Long getLongValue(TaskAttribute attribute) { + String longString = attribute.getValue(); + try { + if (longString != null) { + return Long.parseLong(longString); + } + } catch (NumberFormatException e) { + // ignore + } + return null; + } + + /** + * Returns labelByValue. + */ + public Map<String, String> getOptions(TaskAttribute attribute) { + return attribute.getOptions(); + } + + public IRepositoryPerson getRepositoryPerson(TaskAttribute taskAttribute) { + IRepositoryPerson person = taskRepository.createPerson(taskAttribute.getValue()); + TaskAttribute child = taskAttribute.getMappedAttribute(TaskAttribute.PERSON_NAME); + if (child != null) { + person.setName(getValue(child)); + } + return person; + } + + public List<TaskOperation> getTaskOperations(TaskAttribute operationsAttribute) { + Assert.isNotNull(operationsAttribute); + TaskData taskData = operationsAttribute.getTaskData(); + List<TaskOperation> result = new ArrayList<TaskOperation>(); + for (TaskAttribute taskAttribute : taskData.getRoot().getAttributes().values()) { + if (TaskAttribute.TYPE_OPERATION.equals(taskAttribute.getMetaData().getType()) + && !taskAttribute.getId().equals(mapToRepositoryKey(taskData.getRoot(), TaskAttribute.OPERATION))) { + result.add(TaskOperation.createFrom(taskAttribute)); + } + } + return result; + } + + public TaskOperation getTaskOperation(TaskAttribute taskAttribute) { + Assert.isNotNull(taskAttribute); + return TaskOperation.createFrom(taskAttribute); + } + + public TaskRepository getTaskRepository() { + return taskRepository; + } + + public String getValue(TaskAttribute taskAttribute) { + return taskAttribute.getValue(); + } + + public String getValueLabel(TaskAttribute taskAttribute) { + List<String> labels = getValueLabels(taskAttribute); + StringBuilder sb = new StringBuilder(); + String sep = ""; //$NON-NLS-1$ + for (String value : labels) { + sb.append(sep).append(value); + sep = ", "; //$NON-NLS-1$ + } + return sb.toString(); + } + + public List<String> getValueLabels(TaskAttribute taskAttribute) { + List<String> values = taskAttribute.getValues(); + Map<String, String> options = getOptions(taskAttribute); + List<String> result = new ArrayList<String>(values.size()); + for (String value : values) { + String option = options.get(value); + if (option != null) { + value = option; + } + result.add(value); + } + return result; + } + + public List<String> getValues(TaskAttribute attribute) { + return new ArrayList<String>(attribute.getValues()); + } + + public boolean hasValue(TaskAttribute attribute) { + return attribute.getValues().size() > 0; + } + + public String mapToRepositoryKey(TaskAttribute parent, String key) { + return key; + } + + public void setBooleanValue(TaskAttribute attribute, Boolean value) { + attribute.setValue(Boolean.toString(value)); + } + + public void setDateValue(TaskAttribute attribute, Date date) { + if (date != null) { + attribute.setValue(Long.toString(date.getTime())); + } else { + attribute.clearValues(); + } + } + + public void setIntegerValue(TaskAttribute attribute, Integer value) { + if (value != null) { + attribute.setValue(value.toString()); + } else { + attribute.clearValues(); + } + } + + public void setLongValue(TaskAttribute attribute, Long value) { + if (value != null) { + attribute.setValue(value.toString()); + } else { + attribute.clearValues(); + } + } + + public void setRepositoryPerson(TaskAttribute taskAttribute, IRepositoryPerson person) { + setValue(taskAttribute, person.getPersonId()); + if (person.getName() != null) { + TaskAttribute child = taskAttribute.createAttribute(TaskAttribute.PERSON_NAME); + setValue(child, person.getName()); + } + } + + public void setTaskOperation(TaskAttribute taskAttribute, TaskOperation taskOperation) { + Assert.isNotNull(taskAttribute); + Assert.isNotNull(taskOperation); + TaskOperation.applyTo(taskAttribute, taskOperation.getOperationId(), taskOperation.getLabel()); + } + + public void setValue(TaskAttribute attribute, String value) { + attribute.setValue(value); + } + + public void setValues(TaskAttribute attribute, List<String> values) { + attribute.setValues(values); + } + + public void updateTaskAttachment(ITaskAttachment taskAttachment, TaskAttribute taskAttribute) { + TaskAttachmentMapper.createFrom(taskAttribute).applyTo(taskAttachment); + } + + public void updateTaskComment(ITaskComment taskComment, TaskAttribute taskAttribute) { + TaskCommentMapper.createFrom(taskAttribute).applyTo(taskComment); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttributeMetaData.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttributeMetaData.java new file mode 100644 index 000000000..ab1833a23 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttributeMetaData.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import java.util.Map; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noextend This class is not intended to be subclassed by clients. + */ +public class TaskAttributeMetaData { + +// public enum DetailLevel { +// /** A little bit of detail, e.g. a task showing in the Task List. */ +// LOW, +// /** More detail, e.g. a task showing in a tool tip. */ +// MEDIUM, +// /** A lot of detail, e.g. a task showing in an editor. */ +// //HIGH +// }; + + private final TaskAttribute taskAttribute; + + TaskAttributeMetaData(TaskAttribute taskAttribute) { + this.taskAttribute = taskAttribute; + } + + public TaskAttributeMetaData defaults() { + setLabel(null); + setKind(null); + setReadOnly(true); + setType(TaskAttribute.TYPE_SHORT_TEXT); + return this; + } + + public TaskAttributeMetaData clear() { + taskAttribute.clearMetaDataMap(); + return this; + } + + public String getDefaultOption() { + return taskAttribute.getMetaDatum(TaskAttribute.META_DEFAULT_OPTION); + } + +// public DetailLevel getDetailLevel() { +// try { +// return DetailLevel.valueOf(taskAttribute.getMetaDatum(TaskAttribute.META_DEFAULT_OPTION)); +// } catch (IllegalArgumentException e) { +// return null; +// } +// } + + public String getKind() { + return taskAttribute.getMetaDatum(TaskAttribute.META_ATTRIBUTE_KIND); + } + + public String getLabel() { + return taskAttribute.getMetaDatum(TaskAttribute.META_LABEL); + } + + public String getType() { + return taskAttribute.getMetaDatum(TaskAttribute.META_ATTRIBUTE_TYPE); + } + + public String getValue(String key) { + return taskAttribute.getMetaDatum(key); + } + + public Map<String, String> getValues() { + return taskAttribute.getMetaDataMap(); + } + + public boolean isReadOnly() { + return Boolean.parseBoolean(taskAttribute.getMetaDatum(TaskAttribute.META_READ_ONLY)); + } + + public TaskAttributeMetaData putValue(String key, String value) { + taskAttribute.putMetaDatum(key, value); + return this; + } + + public TaskAttributeMetaData setDefaultOption(String defaultOption) { + if (defaultOption != null) { + taskAttribute.putMetaDatum(TaskAttribute.META_DEFAULT_OPTION, defaultOption); + } else { + taskAttribute.removeMetaDatum(TaskAttribute.META_DEFAULT_OPTION); + } + return this; + } + +// public TaskAttributeMetaData setDetailLevel(DetailLevel detailLevel) { +// if (detailLevel != null) { +// taskAttribute.putMetaDatum(TaskAttribute.META_DETAIL_LEVEL, detailLevel.name()); +// } else { +// taskAttribute.removeMetaDatum(TaskAttribute.META_DETAIL_LEVEL); +// } +// return this; +// } + + public TaskAttributeMetaData setKind(String value) { + if (value != null) { + taskAttribute.putMetaDatum(TaskAttribute.META_ATTRIBUTE_KIND, value); + } else { + taskAttribute.removeMetaDatum(TaskAttribute.META_ATTRIBUTE_KIND); + } + return this; + } + + public TaskAttributeMetaData setLabel(String value) { + if (value != null) { + taskAttribute.putMetaDatum(TaskAttribute.META_LABEL, value); + } else { + taskAttribute.removeMetaDatum(TaskAttribute.META_LABEL); + } + return this; + } + + public TaskAttributeMetaData setReadOnly(boolean value) { + taskAttribute.putMetaDatum(TaskAttribute.META_READ_ONLY, Boolean.toString(value)); + return this; + } + + public TaskAttributeMetaData setType(String value) { + if (value != null) { + taskAttribute.putMetaDatum(TaskAttribute.META_ATTRIBUTE_TYPE, value); + } else { + taskAttribute.removeMetaDatum(TaskAttribute.META_ATTRIBUTE_TYPE); + } + return this; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskCommentMapper.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskCommentMapper.java new file mode 100644 index 000000000..d70c229cf --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskCommentMapper.java @@ -0,0 +1,181 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import java.util.Date; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.mylyn.tasks.core.IRepositoryPerson; +import org.eclipse.mylyn.tasks.core.ITaskComment; + +/** + * @author Rob Elves + * @author Steffen Pingel + * @since 3.0 + */ +public class TaskCommentMapper { + + private IRepositoryPerson author; + + private String commentId; + + private Date creationDate; + + private Integer number; + + private String text; + + private String url; + + public TaskCommentMapper() { + } + + public IRepositoryPerson getAuthor() { + return author; + } + + public String getCommentId() { + return commentId; + } + + public Date getCreationDate() { + return creationDate; + } + + public Integer getNumber() { + return number; + } + + public String getText() { + return text; + } + + public String getUrl() { + return url; + } + + public void setAuthor(IRepositoryPerson author) { + this.author = author; + } + + public void setCommentId(String commentId) { + this.commentId = commentId; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public void setNumber(Integer number) { + this.number = number; + } + + public void setText(String text) { + this.text = text; + } + + public void setUrl(String url) { + this.url = url; + } + + @SuppressWarnings("deprecation") + public static TaskCommentMapper createFrom(TaskAttribute taskAttribute) { + Assert.isNotNull(taskAttribute); + TaskData taskData = taskAttribute.getTaskData(); + TaskAttributeMapper mapper = taskData.getAttributeMapper(); + TaskCommentMapper comment = new TaskCommentMapper(); + comment.setCommentId(mapper.getValue(taskAttribute)); + TaskAttribute child = taskAttribute.getMappedAttribute(TaskAttribute.COMMENT_AUTHOR); + if (child != null) { + IRepositoryPerson person = mapper.getRepositoryPerson(child); + if (person.getName() == null) { + child = taskAttribute.getMappedAttribute(TaskAttribute.COMMENT_AUTHOR_NAME); + if (child != null) { + person.setName(child.getValue()); + } + } + comment.setAuthor(person); + } + child = taskAttribute.getMappedAttribute(TaskAttribute.COMMENT_DATE); + if (child != null) { + comment.setCreationDate(mapper.getDateValue(child)); + } + child = taskAttribute.getMappedAttribute(TaskAttribute.COMMENT_NUMBER); + if (child != null) { + comment.setNumber(mapper.getIntegerValue(child)); + } + child = taskAttribute.getMappedAttribute(TaskAttribute.COMMENT_URL); + if (child != null) { + comment.setUrl(mapper.getValue(child)); + } + child = taskAttribute.getMappedAttribute(TaskAttribute.COMMENT_TEXT); + if (child != null) { + comment.setText(mapper.getValue(child)); + } + return comment; + } + + public void applyTo(TaskAttribute taskAttribute) { + Assert.isNotNull(taskAttribute); + TaskData taskData = taskAttribute.getTaskData(); + TaskAttributeMapper mapper = taskData.getAttributeMapper(); + taskAttribute.getMetaData().defaults().setType(TaskAttribute.TYPE_COMMENT); + if (getCommentId() != null) { + mapper.setValue(taskAttribute, getCommentId()); + } + if (getAuthor() != null) { + TaskAttribute child = taskAttribute.createMappedAttribute(TaskAttribute.COMMENT_AUTHOR); + child.getMetaData().defaults().setType(TaskAttribute.TYPE_PERSON); + mapper.setRepositoryPerson(child, getAuthor()); + } + if (getCreationDate() != null) { + TaskAttribute child = taskAttribute.createMappedAttribute(TaskAttribute.COMMENT_DATE); + child.getMetaData().defaults().setType(TaskAttribute.TYPE_DATE); + mapper.setDateValue(child, getCreationDate()); + } + if (getNumber() != null) { + TaskAttribute child = taskAttribute.createMappedAttribute(TaskAttribute.COMMENT_NUMBER); + child.getMetaData().defaults().setType(TaskAttribute.TYPE_INTEGER); + mapper.setIntegerValue(child, getNumber()); + } + if (getUrl() != null) { + TaskAttribute child = taskAttribute.createMappedAttribute(TaskAttribute.COMMENT_URL); + child.getMetaData().defaults().setType(TaskAttribute.TYPE_URL); + mapper.setValue(child, getUrl()); + } + if (getText() != null) { + TaskAttribute child = taskAttribute.createMappedAttribute(TaskAttribute.COMMENT_TEXT); + child.getMetaData().defaults().setType(TaskAttribute.TYPE_LONG_RICH_TEXT); + mapper.setValue(child, getText()); + taskAttribute.putMetaDatum(TaskAttribute.META_ASSOCIATED_ATTRIBUTE_ID, child.getId()); + } + } + + public void applyTo(ITaskComment taskComment) { + Assert.isNotNull(taskComment); + if (getAuthor() != null) { + taskComment.setAuthor(getAuthor()); + } + if (getCreationDate() != null) { + taskComment.setCreationDate(getCreationDate()); + } + if (getNumber() != null) { + taskComment.setNumber(getNumber()); + } + if (getUrl() != null) { + taskComment.setUrl(getUrl()); + } + if (getText() != null) { + taskComment.setText(getText()); + } + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskData.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskData.java new file mode 100644 index 000000000..464d93fe3 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskData.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.mylyn.tasks.core.AbstractRepositoryConnector; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noextend This class is not intended to be subclassed by clients. + */ +public final class TaskData { + + private final String connectorKind; + + private boolean partial; + + private String version; + + private final String repositoryUrl; + + private final String taskId; + + private final TaskAttributeMapper mapper; + + private final TaskAttribute root; + + public TaskData(TaskAttributeMapper mapper, String connectorKind, String repositoryUrl, String taskId) { + Assert.isNotNull(mapper); + Assert.isNotNull(connectorKind); + Assert.isNotNull(repositoryUrl); + Assert.isNotNull(taskId); + this.mapper = mapper; + this.connectorKind = connectorKind; + this.repositoryUrl = repositoryUrl; + this.taskId = taskId; + this.root = new TaskAttribute(this); + } + + public TaskAttribute getRoot() { + return root; + } + + public String getConnectorKind() { + return connectorKind; + } + + public String getRepositoryUrl() { + return repositoryUrl; + } + + public String getTaskId() { + return taskId; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + /** + * Returns true if this is a new, unsubmitted task; false otherwise. + */ + public boolean isNew() { + return getTaskId().length() == 0; + } + + /** + * Returns true, if this task data does not have all task attributes. + */ + public boolean isPartial() { + return partial; + } + + /** + * Set <code>partial</code> to true to indicate that this task data does not have all task attributes. + * + * @see #isPartial() + * @see AbstractRepositoryConnector#performQuery(org.eclipse.mylyn.tasks.core.TaskRepository, + * org.eclipse.mylyn.tasks.core.IRepositoryQuery, TaskDataCollector, + * org.eclipse.mylyn.tasks.core.sync.ISynchronizationSession, org.eclipse.core.runtime.IProgressMonitor) + * @see AbstractRepositoryConnector#getTaskData(org.eclipse.mylyn.tasks.core.TaskRepository, String, + * org.eclipse.core.runtime.IProgressMonitor) + * @see #isPartial() + */ + public void setPartial(boolean partial) { + this.partial = partial; + } + + public TaskAttributeMapper getAttributeMapper() { + return mapper; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataCollector.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataCollector.java new file mode 100644 index 000000000..4dda0f07a --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataCollector.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +/** + * This class is used for collecting tasks, e.g. when performing queries on a repository. + * + * @author Rob Elves + * @since 3.0 + */ +public abstract class TaskDataCollector { + + /** + * @since 3.0 + */ + public static final int MAX_HITS = 5000; + + public abstract void accept(TaskData taskData); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModel.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModel.java new file mode 100644 index 000000000..f02f0923a --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModel.java @@ -0,0 +1,180 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.ISafeRunnable; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.TaskDataModelEvent.EventKind; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noextend This class is not intended to be subclassed by clients. + */ +public class TaskDataModel { + + private List<TaskDataModelListener> listeners; + + private final ITask task; + + private final TaskRepository taskRepository; + + private final Set<TaskAttribute> unsavedChangedAttributes; + + private final ITaskDataWorkingCopy workingCopy; + + public TaskDataModel(TaskRepository taskRepository, ITask task, ITaskDataWorkingCopy taskDataState) { + Assert.isNotNull(taskRepository); + Assert.isNotNull(task); + Assert.isNotNull(taskDataState); + this.task = task; + this.taskRepository = taskRepository; + this.workingCopy = taskDataState; + this.unsavedChangedAttributes = new HashSet<TaskAttribute>(); + } + + public void addModelListener(TaskDataModelListener listener) { + if (listeners == null) { + listeners = new ArrayList<TaskDataModelListener>(); + } + listeners.add(listener); + } + + /** + * Invoke upon change to attribute value. + * + * @param attribute + * changed attribute + */ + public void attributeChanged(TaskAttribute attribute) { + if (attribute.getParentAttribute() != getTaskData().getRoot()) { + throw new RuntimeException( + "Editing is only supported for attributes that are attached to the root of task data"); //$NON-NLS-1$ + } + + unsavedChangedAttributes.add(attribute); + + if (this.listeners != null) { + final TaskDataModelEvent event = new TaskDataModelEvent(this, EventKind.CHANGED, attribute); + TaskDataModelListener[] listeners = this.listeners.toArray(new TaskDataModelListener[0]); + for (final TaskDataModelListener listener : listeners) { + SafeRunner.run(new ISafeRunnable() { + public void handleException(Throwable e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Listener failed", e)); //$NON-NLS-1$ + } + + public void run() throws Exception { + listener.attributeChanged(event); + } + }); + } + } + } + + public Set<TaskAttribute> getChangedAttributes() { + Set<TaskAttribute> changedAttributes = new LinkedHashSet<TaskAttribute>(); + changedAttributes.addAll(workingCopy.getEditsData().getRoot().getAttributes().values()); + changedAttributes.addAll(unsavedChangedAttributes); + return changedAttributes; + } + + public Set<TaskAttribute> getChangedOldAttributes() { + Set<TaskAttribute> newChangedAttributes = getChangedAttributes(); + Set<TaskAttribute> oldAttributes = new LinkedHashSet<TaskAttribute>(); + TaskData repositoryReadData = workingCopy.getRepositoryData(); + if (repositoryReadData != null) { + for (TaskAttribute taskAttribute : newChangedAttributes) { + TaskAttribute attOld = repositoryReadData.getRoot().getAttribute(taskAttribute.getId()); + if (attOld != null) { + oldAttributes.add(attOld); + } + } + } + return oldAttributes; + } + + public ITask getTask() { + return task; + } + + public TaskData getTaskData() { + return workingCopy.getLocalData(); + } + + public TaskRepository getTaskRepository() { + return taskRepository; + } + + public boolean hasBeenRead() { + return workingCopy.getLastReadData() != null; + } + + public boolean hasIncomingChanges(TaskAttribute taskAttribute) { + TaskData lastReadData = workingCopy.getLastReadData(); + if (lastReadData == null) { + return true; + } + + if (hasOutgoingChanges(taskAttribute)) { + return false; + } + + TaskAttribute oldAttribute = lastReadData.getRoot().getMappedAttribute(taskAttribute.getPath()); + if (oldAttribute == null) { + return true; + } + + return !getTaskData().getAttributeMapper().equals(taskAttribute, oldAttribute); + } + + public boolean hasOutgoingChanges(TaskAttribute taskAttribute) { + return workingCopy.getEditsData().getRoot().getMappedAttribute(taskAttribute.getPath()) != null; + } + + public boolean isDirty() { + return unsavedChangedAttributes.size() > 0 || !workingCopy.isSaved(); + } + + public void refresh(IProgressMonitor monitor) throws CoreException { + workingCopy.refresh(monitor); + } + + public void removeModelListener(TaskDataModelListener listener) { + listeners.remove(listener); + } + + public void revert() { + workingCopy.revert(); + unsavedChangedAttributes.clear(); + } + + public void save(IProgressMonitor monitor) throws CoreException { + workingCopy.save(unsavedChangedAttributes, monitor); + unsavedChangedAttributes.clear(); + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModelEvent.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModelEvent.java new file mode 100644 index 000000000..cf96be225 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModelEvent.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noextend This class is not intended to be subclassed by clients. + * @noinstantiate This class is not intended to be instantiated by clients. + */ +public final class TaskDataModelEvent { + + public enum EventKind { + CHANGED + } + + private final EventKind kind; + + private final TaskDataModel model; + + private final TaskAttribute taskAttribute;; + + public TaskDataModelEvent(TaskDataModel model, EventKind kind, TaskAttribute taskAttribute) { + this.model = model; + this.kind = kind; + this.taskAttribute = taskAttribute; + } + + public EventKind getKind() { + return kind; + } + + public TaskDataModel getModel() { + return model; + } + + public TaskAttribute getTaskAttribute() { + return taskAttribute; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModelListener.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModelListener.java new file mode 100644 index 000000000..adde25198 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModelListener.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +/** + * @author Steffen Pingel + * @since 3.0 + */ +public abstract class TaskDataModelListener { + + /** + * @since 3.0 + */ + public abstract void attributeChanged(TaskDataModelEvent event); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskMapper.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskMapper.java new file mode 100644 index 000000000..418b526a2 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskMapper.java @@ -0,0 +1,465 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import java.util.Date; +import java.util.List; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.ITaskMapping; +import org.eclipse.mylyn.tasks.core.ITask.PriorityLevel; + +/** + * @author Steffen Pingel + * @since 3.0 + */ +// TODO 3.2 add setTaskKey() method +public class TaskMapper implements ITaskMapping { + + private final boolean createNonExistingAttributes; + + private final TaskData taskData; + + public TaskMapper(TaskData taskData) { + this(taskData, false); + } + + public TaskMapper(TaskData taskData, boolean createNonExistingAttributes) { + this.createNonExistingAttributes = createNonExistingAttributes; + Assert.isNotNull(taskData); + this.taskData = taskData; + } + + public boolean applyTo(ITask task) { + boolean changed = false; + if (hasChanges(task.getCompletionDate(), getCompletionDate(), TaskAttribute.DATE_COMPLETION)) { + task.setCompletionDate(getCompletionDate()); + changed = true; + } + if (hasChanges(task.getCreationDate(), getCreationDate(), TaskAttribute.DATE_CREATION)) { + task.setCreationDate(getCreationDate()); + changed = true; + } + if (hasChanges(task.getModificationDate(), getModificationDate(), TaskAttribute.DATE_MODIFICATION)) { + task.setModificationDate(getModificationDate()); + changed = true; + } + if (hasChanges(task.getDueDate(), getDueDate(), TaskAttribute.DATE_DUE)) { + task.setDueDate(getDueDate()); + changed = true; + } + if (hasChanges(task.getOwner(), getOwner(), TaskAttribute.USER_ASSIGNED)) { + task.setOwner(getOwner()); + changed = true; + } + if (hasChanges(task.getPriority(), getPriorityLevelString(), TaskAttribute.PRIORITY)) { + task.setPriority(getPriorityLevelString()); + changed = true; + } + if (hasChanges(task.getSummary(), getSummary(), TaskAttribute.SUMMARY)) { + task.setSummary(getSummary()); + changed = true; + } + if (hasChanges(task.getTaskKey(), getTaskKey(), TaskAttribute.TASK_KEY)) { + task.setTaskKey(getTaskKey()); + changed = true; + } + if (hasChanges(task.getTaskKind(), getTaskKind(), TaskAttribute.TASK_KIND)) { + task.setTaskKind(getTaskKind()); + changed = true; + } + if (hasChanges(task.getUrl(), getTaskUrl(), TaskAttribute.TASK_URL)) { + task.setUrl(getTaskUrl()); + changed = true; + } + return changed; + } + + private String getPriorityLevelString() { + return (getPriorityLevel() != null) ? getPriorityLevel().toString() : PriorityLevel.getDefault().toString(); + } + + private boolean hasChanges(Object existingValue, Object newValue, String attributeId) { + TaskAttribute attribute = taskData.getRoot().getMappedAttribute(attributeId); + if (attribute != null) { + return areNotEquals(existingValue, newValue); + } + return false; + } + + private boolean areNotEquals(Object existingProperty, Object newProperty) { + return (existingProperty != null) ? !existingProperty.equals(newProperty) : newProperty != null; + } + + private void copyAttributeValue(TaskAttribute sourceAttribute, TaskAttribute targetAttribute) { + if (targetAttribute == null) { + return; + } + if (!targetAttribute.getMetaData().isReadOnly()) { + targetAttribute.clearValues(); + if (targetAttribute.getOptions().size() > 0) { + List<String> values = sourceAttribute.getValues(); + for (String value : values) { + if (targetAttribute.getOptions().containsKey(value)) { + targetAttribute.addValue(value); + } + } + } else { + List<String> values = sourceAttribute.getValues(); + for (String value : values) { + targetAttribute.addValue(value); + } + } + } + } + + /** + * TODO update comment Sets attribute values from <code>sourceTaskData</code> on <code>targetTaskData</code>. Sets + * the following attributes: + * <ul> + * <li>summary + * <li>description + * </ul> + * Other attribute values are only set if they exist on <code>sourceTaskData</code> and <code>targetTaskData</code>. + * + * @param sourceTaskData + * the source task data values are copied from, the connector kind of repository of + * <code>sourceTaskData</code> can be different from <code>targetTaskData</code> + * @param targetTaskData + * the target task data values are copied to, the connector kind matches the one of this task data + * handler + * @since 2.2 + */ + public void merge(ITaskMapping source) { + if (source.getTaskData() != null && this.getTaskData() != null + && source.getTaskData().getConnectorKind().equals(this.getTaskData().getConnectorKind())) { + // task data objects are from the same connector, copy all attributes + for (TaskAttribute sourceAttribute : source.getTaskData().getRoot().getAttributes().values()) { + copyAttributeValue(sourceAttribute, this.getTaskData().getRoot().getAttribute(sourceAttribute.getId())); + } + } else { + if (source.getCc() != null) { + setCc(source.getCc()); + } + if (source.getDescription() != null) { + setDescription(source.getDescription()); + } + if (source.getComponent() != null) { + setComponent(source.getComponent()); + } + if (source.getKeywords() != null) { + setKeywords(source.getKeywords()); + } + if (source.getOwner() != null) { + setOwner(source.getOwner()); + } + if (source.getPriorityLevel() != null) { + setPriorityLevel(source.getPriorityLevel()); + } + if (source.getProduct() != null) { + setProduct(source.getProduct()); + } + if (source.getSeverity() != null) { + setSeverity(source.getSeverity()); + } + if (source.getSummary() != null) { + setSummary(source.getSummary()); + } + if (source.getVersion() != null) { + setVersion(source.getVersion()); + } + } + } + + private TaskAttribute createAttribute(String attributeKey, String type) { + attributeKey = taskData.getAttributeMapper().mapToRepositoryKey(taskData.getRoot(), attributeKey); + TaskAttribute attribute = taskData.getRoot().createAttribute(attributeKey); + attribute.getMetaData().defaults().setType(type); + return attribute; + } + + public List<String> getCc() { + return getValues(TaskAttribute.USER_CC); + } + + public Date getCompletionDate() { + return getDateValue(TaskAttribute.DATE_COMPLETION); + } + + public String getComponent() { + return getValue(TaskAttribute.COMPONENT); + } + + public Date getCreationDate() { + return getDateValue(TaskAttribute.DATE_CREATION); + } + + private Date getDateValue(String attributeKey) { + TaskAttribute attribute = taskData.getRoot().getMappedAttribute(attributeKey); + if (attribute != null) { + return taskData.getAttributeMapper().getDateValue(attribute); + } + return null; + } + + public String getDescription() { + return getValue(TaskAttribute.DESCRIPTION); + } + + public Date getDueDate() { + return getDateValue(TaskAttribute.DATE_DUE); + } + + public List<String> getKeywords() { + return getValues(TaskAttribute.KEYWORDS); + } + + private TaskAttribute getWriteableAttribute(String attributeKey, String type) { + TaskAttribute attribute = taskData.getRoot().getMappedAttribute(attributeKey); + if (createNonExistingAttributes) { + if (attribute == null) { + attribute = createAttribute(attributeKey, type); + } + } else if (attribute != null && attribute.getMetaData().isReadOnly()) { + return null; + } + return attribute; + } + + public Date getModificationDate() { + return getDateValue(TaskAttribute.DATE_MODIFICATION); + } + + public String getOwner() { + return getValue(TaskAttribute.USER_ASSIGNED); + } + + public String getPriority() { + return getValue(TaskAttribute.PRIORITY); + } + + public PriorityLevel getPriorityLevel() { + String value = getPriority(); + return (value != null) ? PriorityLevel.fromString(value) : null; + } + + public String getProduct() { + return getValue(TaskAttribute.PRODUCT); + } + + public String getReporter() { + return getValue(TaskAttribute.USER_REPORTER); + } + + public String getResolution() { + return getValue(TaskAttribute.RESOLUTION); + } + + /** + * @since 3.2 + */ + public String getSeverity() { + return getValue(TaskAttribute.SEVERITY); + } + + public String getSummary() { + return getValue(TaskAttribute.SUMMARY); + } + + public String getStatus() { + return getValue(TaskAttribute.STATUS); + } + + public TaskData getTaskData() { + return taskData; + } + + public String getTaskKey() { + return getValue(TaskAttribute.TASK_KEY); + } + + public String getTaskKind() { + return getValue(TaskAttribute.TASK_KIND); + } + + public String getTaskStatus() { + return getValue(TaskAttribute.STATUS); + } + + public String getTaskUrl() { + return getValue(TaskAttribute.TASK_URL); + } + + public String getValue(String attributeKey) { + TaskAttribute attribute = taskData.getRoot().getMappedAttribute(attributeKey); + if (attribute != null) { + return taskData.getAttributeMapper().getValueLabel(attribute); + } + return null; + } + + private List<String> getValues(String attributeKey) { + TaskAttribute attribute = taskData.getRoot().getMappedAttribute(attributeKey); + if (attribute != null) { + return taskData.getAttributeMapper().getValueLabels(attribute); + } + return null; + } + + /** + * @since 3.2 + */ + public String getVersion() { + return getValue(TaskAttribute.VERSION); + } + + public boolean hasChanges(ITask task) { + boolean changed = false; + changed |= hasChanges(task.getCompletionDate(), getCompletionDate(), TaskAttribute.DATE_COMPLETION); + changed |= hasChanges(task.getCreationDate(), getCreationDate(), TaskAttribute.DATE_CREATION); + changed |= hasChanges(task.getModificationDate(), getModificationDate(), TaskAttribute.DATE_MODIFICATION); + changed |= hasChanges(task.getDueDate(), getDueDate(), TaskAttribute.DATE_DUE); + changed |= hasChanges(task.getOwner(), getOwner(), TaskAttribute.USER_ASSIGNED); + changed |= hasChanges(task.getPriority(), getPriorityLevelString(), TaskAttribute.PRIORITY); + changed |= hasChanges(task.getSummary(), getSummary(), TaskAttribute.SUMMARY); + changed |= hasChanges(task.getTaskKey(), getTaskKey(), TaskAttribute.TASK_KEY); + changed |= hasChanges(task.getTaskKind(), getTaskKind(), TaskAttribute.TASK_KIND); + changed |= hasChanges(task.getUrl(), getTaskUrl(), TaskAttribute.TASK_URL); + return changed; + } + +// private boolean hasChanges(Object value, String attributeKey) { +// TaskAttribute attribute = taskData.getRoot().getMappedAttribute(attributeKey); +// if (attribute != null) { +// if (TaskAttribute.TYPE_BOOLEAN.equals(attribute.getMetaData().getType())) { +// return areNotEquals(value, taskData.getAttributeMapper().getBooleanValue(attribute)); +// } else if (TaskAttribute.TYPE_DATE.equals(attribute.getMetaData().getType())) { +// return areNotEquals(value, taskData.getAttributeMapper().getDateValue(attribute)); +// } else if (TaskAttribute.TYPE_INTEGER.equals(attribute.getMetaData().getType())) { +// return areNotEquals(value, taskData.getAttributeMapper().getIntegerValue(attribute)); +// } else { +// return areNotEquals(value, taskData.getAttributeMapper().getValue(attribute)); +// } +// } +// return false; +// } + + public void setCc(List<String> cc) { + setValues(TaskAttribute.USER_CC, cc); + } + + public void setCompletionDate(Date dateCompleted) { + setDateValue(TaskAttribute.DATE_COMPLETION, dateCompleted); + } + + public void setComponent(String component) { + setValue(TaskAttribute.COMPONENT, component); + } + + public void setCreationDate(Date dateCreated) { + setDateValue(TaskAttribute.DATE_CREATION, dateCreated); + } + + private TaskAttribute setDateValue(String attributeKey, Date value) { + TaskAttribute attribute = getWriteableAttribute(attributeKey, TaskAttribute.TYPE_DATE); + if (attribute != null) { + taskData.getAttributeMapper().setDateValue(attribute, value); + } + return attribute; + } + + public void setDescription(String description) { + setValue(TaskAttribute.DESCRIPTION, description); + } + + public void setDueDate(Date value) { + setDateValue(TaskAttribute.DATE_DUE, value); + } + + public void setKeywords(List<String> keywords) { + setValues(TaskAttribute.KEYWORDS, keywords); + } + + public void setModificationDate(Date dateModified) { + setDateValue(TaskAttribute.DATE_MODIFICATION, dateModified); + } + + // TODO use Person class? + public void setOwner(String owner) { + setValue(TaskAttribute.USER_ASSIGNED, owner); + } + + public void setPriority(String priority) { + setValue(TaskAttribute.PRIORITY, priority); + } + + public void setPriorityLevel(PriorityLevel priority) { + setPriority(priority.toString()); + } + + public void setProduct(String product) { + setValue(TaskAttribute.PRODUCT, product); + } + + // TODO use Person class? + public void setReporter(String reporter) { + setValue(TaskAttribute.USER_REPORTER, reporter); + } + + /** + * @since 3.2 + */ + public void setSeverity(String severity) { + setValue(TaskAttribute.SEVERITY, severity); + } + + public void setSummary(String summary) { + setValue(TaskAttribute.SUMMARY, summary); + } + + public void setStatus(String status) { + setValue(TaskAttribute.STATUS, status); + } + + public void setTaskKind(String taskKind) { + setValue(TaskAttribute.TASK_KIND, taskKind); + } + + public void setTaskUrl(String taskUrl) { + setValue(TaskAttribute.TASK_URL, taskUrl); + } + + /** + * @since 3.2 + */ + public void setVersion(String version) { + setValue(TaskAttribute.VERSION, version); + } + + public TaskAttribute setValue(String attributeKey, String value) { + TaskAttribute attribute = getWriteableAttribute(attributeKey, TaskAttribute.TYPE_SHORT_TEXT); + if (attribute != null) { + taskData.getAttributeMapper().setValue(attribute, value); + } + return attribute; + } + + private TaskAttribute setValues(String attributeKey, List<String> values) { + TaskAttribute attribute = getWriteableAttribute(attributeKey, TaskAttribute.TYPE_SHORT_TEXT); + if (attribute != null) { + taskData.getAttributeMapper().setValues(attribute, values); + } + return attribute; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskOperation.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskOperation.java new file mode 100644 index 000000000..e44d8262d --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskOperation.java @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import org.eclipse.core.runtime.Assert; + +/** + * @author Rob Elves + * @author Steffen Pingel + * @since 3.0 + */ +public class TaskOperation { + + /** + * @since 3.0 + */ + public static void applyTo(TaskAttribute taskAttribute, String operationId, String label) { + TaskData taskData = taskAttribute.getTaskData(); + taskData.getAttributeMapper().setValue(taskAttribute, operationId); + taskAttribute.getMetaData().defaults().setType(TaskAttribute.TYPE_OPERATION).setLabel(label); + } + + /** + * @since 3.0 + */ + public static TaskOperation createFrom(TaskAttribute taskAttribute) { + Assert.isNotNull(taskAttribute); + TaskData taskData = taskAttribute.getTaskData(); + TaskOperation operation = new TaskOperation(taskData.getConnectorKind(), taskData.getRepositoryUrl(), + taskData.getTaskId(), taskAttribute.getValue()); + operation.setLabel(taskAttribute.getMetaData().getLabel()); + operation.setTaskAttribute(taskAttribute); + return operation; + } + + private final String connectorKind; + + private String label; + + private final String operationId; + + private final String repositoryUrl; + + private TaskAttribute taskAttribute; + + private final String taskId; + + /** + * @since 3.0 + */ + public TaskOperation(String connectorKind, String repositoryUrl, String taskId, String operationId) { + Assert.isNotNull(connectorKind); + Assert.isNotNull(repositoryUrl); + Assert.isNotNull(taskId); + Assert.isNotNull(operationId); + this.connectorKind = connectorKind; + this.repositoryUrl = repositoryUrl; + this.taskId = taskId; + this.operationId = operationId; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + TaskOperation other = (TaskOperation) obj; + if (!connectorKind.equals(other.connectorKind)) { + return false; + } + if (!operationId.equals(other.operationId)) { + return false; + } + if (!repositoryUrl.equals(other.repositoryUrl)) { + return false; + } + if (!taskId.equals(other.taskId)) { + return false; + } + return true; + } + + /** + * @since 3.0 + */ + public String getConnectorKind() { + return connectorKind; + } + + /** + * @since 3.0 + */ + public String getLabel() { + return label; + } + + /** + * @since 3.0 + */ + public String getOperationId() { + return operationId; + } + + public String getRepositoryUrl() { + return repositoryUrl; + } + + /** + * @since 3.0 + */ + public TaskAttribute getTaskAttribute() { + return taskAttribute; + } + + /** + * @since 3.0 + */ + public String getTaskId() { + return taskId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + connectorKind.hashCode(); + result = prime * result + operationId.hashCode(); + result = prime * result + repositoryUrl.hashCode(); + result = prime * result + taskId.hashCode(); + return result; + } + + /** + * @since 3.0 + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * @since 3.0 + */ + public void setTaskAttribute(TaskAttribute taskAttribute) { + this.taskAttribute = taskAttribute; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskRelation.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskRelation.java new file mode 100644 index 000000000..55cdfb6cd --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskRelation.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import org.eclipse.core.runtime.Assert; + +/** + * @author Steffen Pingel + * @since 3.0 + */ +public class TaskRelation { + + private final String taskId; + + private final Kind kind; + + private final Direction direction; + + /** + * @since 3.0 + */ + public enum Direction { + INWARD, OUTWARD + }; + + /** + * @since 3.0 + */ + public enum Kind { + CONTAINMENT, DEPENDENCY, DUPLICATE + } + + private TaskRelation(Kind kind, Direction direction, String taskId) { + Assert.isNotNull(kind); + Assert.isNotNull(direction); + Assert.isNotNull(taskId); + this.kind = kind; + this.direction = direction; + this.taskId = taskId; + } + + /** + * @since 3.0 + */ + public String getTaskId() { + return taskId; + } + + /** + * @since 3.0 + */ + public Kind getKind() { + return kind; + } + + /** + * @since 3.0 + */ + public Direction getDirection() { + return direction; + } + + /** + * @since 3.0 + */ + public static TaskRelation parentTask(String taskId) { + return new TaskRelation(Kind.CONTAINMENT, Direction.INWARD, taskId); + } + + /** + * @since 3.0 + */ + public static TaskRelation subtask(String taskId) { + return new TaskRelation(Kind.CONTAINMENT, Direction.OUTWARD, taskId); + } + + /** + * @since 3.0 + */ + public static TaskRelation dependency(String taskId, Direction direction) { + return new TaskRelation(Kind.DEPENDENCY, direction, taskId); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + direction.hashCode(); + result = prime * result + kind.hashCode(); + result = prime * result + taskId.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + TaskRelation other = (TaskRelation) obj; + return direction.equals(other.direction) && kind.equals(other.kind) && taskId.equals(other.taskId); + } +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/ISynchronizationSession.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/ISynchronizationSession.java new file mode 100644 index 000000000..a700412c2 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/ISynchronizationSession.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.sync; + +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.ITaskDataManager; +import org.eclipse.mylyn.tasks.core.data.TaskData; + +/** + * @since 3.0 + * @author Steffen Pingel + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface ISynchronizationSession { + + /** + * @since 3.0 + */ + public abstract Set<ITask> getChangedTasks(); + + /** + * @since 3.0 + */ + public abstract Object getData(); + + /** + * @since 3.0 + */ + public abstract IStatus getStatus(); + + /** + * @since 3.0 + */ + public abstract ITaskDataManager getTaskDataManager(); + + /** + * @since 3.0 + */ + public abstract TaskRepository getTaskRepository(); + + /** + * @since 3.0 + */ + public abstract Set<ITask> getTasks(); + + /** + * @since 3.0 + */ + public abstract boolean isFullSynchronization(); + + /** + * @since 3.0 + */ + public abstract boolean isUser(); + + /** + * @since 3.0 + */ + public abstract boolean needsPerformQueries(); + + /** + * @since 3.0 + */ + public abstract void setData(Object data); + + /** + * @since 3.0 + */ + public abstract void setNeedsPerformQueries(boolean performQueries); + + /** + * @since 3.0 + */ + public abstract void markStale(ITask task); + + /** + * @since 3.0 + */ + // TODO m4.0 pass TaskDataCollector to preSynchronization() instead + public abstract void putTaskData(ITask task, TaskData taskData) throws CoreException; + +}
\ No newline at end of file diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJob.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJob.java new file mode 100644 index 000000000..20da22381 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJob.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.sync; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.ISafeRunnable; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.core.runtime.Status; +import org.eclipse.mylyn.commons.core.DelegatingProgressMonitor; +import org.eclipse.mylyn.commons.core.IDelegatingProgressMonitor; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.commons.net.Policy; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.RepositoryResponse; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noextend This class is not intended to be subclassed by clients. + */ +public abstract class SubmitJob extends TaskJob { + + private final List<SubmitJobListener> submitJobListeners = Collections.synchronizedList(new ArrayList<SubmitJobListener>()); + + /** + * @since 3.2 + */ + protected final IDelegatingProgressMonitor monitor; + + /** + * @since 3.0 + */ + public SubmitJob(String name) { + super(name); + this.monitor = new DelegatingProgressMonitor(); + } + + /** + * @since 3.0 + */ + public void addSubmitJobListener(SubmitJobListener listener) { + submitJobListeners.add(listener); + } + + /** + * @since 3.0 + */ + public void removeSubmitJobListener(SubmitJobListener listener) { + submitJobListeners.remove(listener); + } + + /** + * @since 3.0 + */ + protected SubmitJobListener[] getSubmitJobListeners() { + return submitJobListeners.toArray(new SubmitJobListener[0]); + } + + /** + * @since 3.0 + */ + protected void fireTaskSubmitted(final IProgressMonitor monitor) throws CoreException { + SubmitJobListener[] listeners = submitJobListeners.toArray(new SubmitJobListener[0]); + if (listeners.length > 0) { + final SubmitJobEvent event = new SubmitJobEvent(this); + for (final SubmitJobListener listener : listeners) { + listener.taskSubmitted(event, Policy.subMonitorFor(monitor, 100)); + } + } + } + + /** + * @since 3.0 + */ + protected void fireTaskSynchronized(final IProgressMonitor monitor) throws CoreException { + SubmitJobListener[] listeners = submitJobListeners.toArray(new SubmitJobListener[0]); + if (listeners.length > 0) { + final SubmitJobEvent event = new SubmitJobEvent(this); + for (final SubmitJobListener listener : listeners) { + listener.taskSynchronized(event, Policy.subMonitorFor(monitor, 100)); + } + } + } + + /** + * @since 3.0 + */ + protected void fireDone() { + SubmitJobListener[] listeners = submitJobListeners.toArray(new SubmitJobListener[0]); + if (listeners.length > 0) { + final SubmitJobEvent event = new SubmitJobEvent(this); + for (final SubmitJobListener listener : listeners) { + SafeRunner.run(new ISafeRunnable() { + public void handleException(Throwable e) { + StatusHandler.log(new Status(IStatus.ERROR, ITasksCoreConstants.ID_PLUGIN, "Listener failed", e)); //$NON-NLS-1$ + } + + public void run() throws Exception { + listener.done(event); + } + }); + } + } + } + + /** + * @since 3.0 + */ + public abstract ITask getTask(); + + /** + * Returns the connector specific result of the submission. + * + * @return the response from the repository, null if no response was received or the submission failed + * @since 3.2 + */ + public abstract RepositoryResponse getResponse(); + + /** + * @since 3.2 + */ + public IDelegatingProgressMonitor getMonitor() { + return monitor; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJobEvent.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJobEvent.java new file mode 100644 index 000000000..1143cf3f2 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJobEvent.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.sync; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noinstantiate This class is not intended to be instantiated by clients. + * @noextend This class is not intended to be subclassed by clients. + */ +public class SubmitJobEvent { + + private final SubmitJob job; + + /** + * @since 3.0 + */ + public SubmitJobEvent(SubmitJob job) { + this.job = job; + } + + /** + * @since 3.0 + */ + public SubmitJob getJob() { + return job; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJobListener.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJobListener.java new file mode 100644 index 000000000..1cba03c17 --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJobListener.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.sync; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * @author Steffen Pingel + * @since 3.0 + */ +public abstract class SubmitJobListener { + + /** + * @since 3.0 + */ + public abstract void taskSubmitted(SubmitJobEvent event, IProgressMonitor monitor) throws CoreException; + + /** + * @since 3.0 + */ + public abstract void taskSynchronized(SubmitJobEvent event, IProgressMonitor monitor) throws CoreException; + + /** + * @since 3.0 + */ + public abstract void done(SubmitJobEvent event); + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SynchronizationJob.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SynchronizationJob.java new file mode 100644 index 000000000..d8ebb7c6a --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SynchronizationJob.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.sync; + +import org.eclipse.core.runtime.jobs.Job; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noextend This class is not intended to be subclassed by clients. + */ +public abstract class SynchronizationJob extends Job { + +// private boolean changedTasksSynchronization = true; + + private boolean fullSynchronization = false; + + /** + * @since 3.0 + */ + public SynchronizationJob(String name) { + super(name); + } + +// public boolean isChangedTasksSynchronization() { +// return changedTasksSynchronization; +// } + + /** + * @since 3.0 + */ + public boolean isFullSynchronization() { + return fullSynchronization; + } + +// public void setChangedTasksSynchronization(boolean synchronizeChangedTasks) { +// this.changedTasksSynchronization = synchronizeChangedTasks; +// } + + /** + * @since 3.0 + */ + public void setFullSynchronization(boolean fullSynchronization) { + this.fullSynchronization = fullSynchronization; + } + +} diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/TaskJob.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/TaskJob.java new file mode 100644 index 000000000..c292bc4ea --- /dev/null +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/TaskJob.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2004, 2008 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.sync; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.jobs.Job; + +/** + * @author Steffen Pingel + * @since 3.0 + * @noextend This class is not intended to be subclassed by clients. + */ +public abstract class TaskJob extends Job { + + /** + * @since 3.0 + */ + public TaskJob(String name) { + super(name); + } + + /** + * @since 3.0 + */ + public abstract IStatus getStatus(); + +} |