Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcvs2git2009-05-21 05:10:01 +0000
committercvs2git2009-05-21 05:10:01 +0000
commit718ec483ef1166b496fb1ee510121e01d37f2f45 (patch)
tree3926d1e7e56c88711bb8f4c560e5f536f2e99091
parent943cfc79210399d0b537b9330a7cc6136de85cc2 (diff)
downloadorg.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
-rw-r--r--org.eclipse.mylyn.tasks.core/.settings/org.eclipse.jdt.core.prefs342
-rw-r--r--org.eclipse.mylyn.tasks.core/.settings/org.eclipse.jdt.ui.prefs63
-rw-r--r--org.eclipse.mylyn.tasks.core/META-INF/MANIFEST.MF20
-rw-r--r--org.eclipse.mylyn.tasks.core/about.html27
-rw-r--r--org.eclipse.mylyn.tasks.core/build.properties16
-rw-r--r--org.eclipse.mylyn.tasks.core/plugin.properties3
-rw-r--r--org.eclipse.mylyn.tasks.core/plugin.xml30
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/provisional/tasks/core/RepositoryClientManager.java166
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/provisional/tasks/core/TasksUtil.java67
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractSearchHandler.java32
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTask.java513
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTaskCategory.java26
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AbstractTaskContainer.java183
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AttributeMap.java47
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/AutomaticRepositoryTaskContainer.java60
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/CommentQuoter.java98
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DateRange.java170
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DayDateRange.java88
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/DefaultTaskMapping.java45
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/IRepositoryConstants.java40
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/IRepositoryModelListener.java24
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskJobFactory.java50
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskList.java85
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskListChangeListener.java26
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskListRunnable.java23
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskRepositoryElement.java23
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITaskRepositoryFilter.java58
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITasksCoreConstants.java137
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ITransferList.java46
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/LocalRepositoryConnector.java118
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/LocalTask.java55
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/Messages.java64
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/Person.java36
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryExternalizationParticipant.java105
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryModel.java137
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryPerson.java66
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryQuery.java135
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryTaskHandleUtil.java57
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/RepositoryTemplateManager.java60
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/SaxRepositoriesContentHandler.java59
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/SaxRepositoriesWriter.java193
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/ScheduledTaskContainer.java221
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivationHistory.java127
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivityManager.java849
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskActivityUtil.java263
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskAttachment.java163
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskCategory.java57
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskComment.java115
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskContainerDelta.java116
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskExternalizationException.java30
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskGroup.java46
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskList.java723
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoriesExternalizer.java134
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryAdapter.java39
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryLocation.java62
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskRepositoryManager.java413
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TaskTask.java42
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/TransferList.java108
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UncategorizedTaskContainer.java51
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UnmatchedTaskContainer.java33
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/UnsubmittedTaskContainer.java31
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/WeekDateRange.java168
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ElementHandler.java123
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/FileTaskAttachmentSource.java157
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ITaskDataConstants.java102
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/ITaskDataManagerListener.java23
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataExternalizer.java127
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataManager.java548
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataManagerEvent.java93
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataState.java205
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStateReader.java626
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStateWriter.java124
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TaskDataStore.java166
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/data/TextTaskAttachmentSource.java59
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/AbstractExternalizationParticipant.java109
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/DelegatingTaskExternalizer.java805
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/ExternalizationManager.java244
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/IExternalizationContext.java26
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/IExternalizationParticipant.java31
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/Messages.java35
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/TaskListExternalizationParticipant.java183
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/TaskListExternalizer.java320
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/externalization/messages.properties5
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/messages.properties26
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/Messages.java69
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SubmitTaskAttachmentJob.java114
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SubmitTaskJob.java134
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizationSession.java143
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeQueriesJob.java322
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeRepositoriesJob.java176
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/SynchronizeTasksJob.java330
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/internal/tasks/core/sync/messages.properties24
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractDuplicateDetector.java54
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractRepositoryConnector.java281
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/AbstractTaskListMigrator.java40
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IAttributeContainer.java30
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryElement.java42
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryListener.java50
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryManager.java42
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryModel.java50
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryPerson.java52
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/IRepositoryQuery.java51
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITask.java338
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivationListener.java40
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivityListener.java37
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskActivityManager.java85
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskAttachment.java146
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskComment.java103
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskContainer.java33
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/ITaskMapping.java81
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryResponse.java47
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryStatus.java198
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/RepositoryTemplate.java75
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskActivationAdapter.java32
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskActivityAdapter.java26
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskMapping.java219
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskRepository.java836
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/TaskRepositoryLocationFactory.java30
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskAttachmentHandler.java39
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskAttachmentSource.java39
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/AbstractTaskDataHandler.java96
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/ITaskDataManager.java56
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/ITaskDataWorkingCopy.java82
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentMapper.java298
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentModel.java90
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttachmentPartSource.java56
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttribute.java563
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttributeMapper.java276
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttributeMetaData.java142
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskCommentMapper.java181
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskData.java107
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataCollector.java29
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModel.java180
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModelEvent.java50
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskDataModelListener.java25
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskMapper.java465
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskOperation.java162
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskRelation.java117
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/ISynchronizationSession.java97
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJob.java142
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJobEvent.java38
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SubmitJobListener.java38
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/SynchronizationJob.java56
-rw-r--r--org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/sync/TaskJob.java36
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 (&quot;Content&quot;). Unless otherwise
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;). 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, &quot;Program&quot; 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 (&quot;Redistributor&quot;) 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();
+
+}

Back to the top