diff options
author | spingel | 2008-06-13 10:27:07 +0000 |
---|---|---|
committer | spingel | 2008-06-13 10:27:07 +0000 |
commit | b4996338b46ec9be20eddec644f74ccbc47bd42c (patch) | |
tree | 8a1b4007d8cd67d598802ec410a7b32215b22884 /org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core | |
parent | 6e6c278e32df5acf18d6c4636f8e0b86fe15a9ee (diff) | |
download | org.eclipse.mylyn.tasks-b4996338b46ec9be20eddec644f74ccbc47bd42c.tar.gz org.eclipse.mylyn.tasks-b4996338b46ec9be20eddec644f74ccbc47bd42c.tar.xz org.eclipse.mylyn.tasks-b4996338b46ec9be20eddec644f74ccbc47bd42c.zip |
NEW - bug 235011: port Trac to Mylyn 3.0 APIs
https://bugs.eclipse.org/bugs/show_bug.cgi?id=235011
Diffstat (limited to 'org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core')
15 files changed, 1001 insertions, 937 deletions
diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttachmentHandler.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttachmentHandler.java index 208b1ab47..5cba7aee3 100644 --- a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttachmentHandler.java +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttachmentHandler.java @@ -13,19 +13,19 @@ import java.io.InputStream; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; -import org.eclipse.mylyn.internal.tasks.core.deprecated.AbstractAttachmentHandler; -import org.eclipse.mylyn.internal.tasks.core.deprecated.ITaskAttachment; -import org.eclipse.mylyn.internal.tasks.core.deprecated.RepositoryAttachment; -import org.eclipse.mylyn.internal.tasks.core.deprecated.RepositoryTaskAttribute; import org.eclipse.mylyn.internal.trac.core.model.TracTicket; import org.eclipse.mylyn.tasks.core.ITask; import org.eclipse.mylyn.tasks.core.RepositoryStatus; 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.TaskAttachmentMapper; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; /** * @author Steffen Pingel */ -public class TracAttachmentHandler extends AbstractAttachmentHandler { +public class TracAttachmentHandler extends AbstractTaskAttachmentHandler { private final TracRepositoryConnector connector; @@ -34,18 +34,19 @@ public class TracAttachmentHandler extends AbstractAttachmentHandler { } @Override - public InputStream getAttachmentAsStream(TaskRepository repository, RepositoryAttachment attachment, + public InputStream getContent(TaskRepository repository, ITask task, TaskAttribute attachmentAttribute, IProgressMonitor monitor) throws CoreException { - String filename = attachment.getAttributeValue(RepositoryTaskAttribute.ATTACHMENT_FILENAME); - if (filename == null) { - throw new CoreException(new RepositoryStatus(repository.getRepositoryUrl(), IStatus.ERROR, TracCorePlugin.ID_PLUGIN, - RepositoryStatus.ERROR_REPOSITORY, "Attachment download from " + repository.getRepositoryUrl() - + " failed, missing attachment filename.")); + TaskAttachmentMapper mapper = TaskAttachmentMapper.createFrom(attachmentAttribute); + String filename = mapper.getFileName(); + if (filename == null || filename.length() == 0) { + throw new CoreException(new RepositoryStatus(repository.getRepositoryUrl(), IStatus.ERROR, + TracCorePlugin.ID_PLUGIN, RepositoryStatus.ERROR_REPOSITORY, "Attachment download from " + + repository.getRepositoryUrl() + " failed, missing attachment filename.")); } try { ITracClient client = connector.getClientManager().getTracClient(repository); - int id = Integer.parseInt(attachment.getTaskId()); + int id = Integer.parseInt(task.getTaskId()); return client.getAttachmentData(id, filename, monitor); } catch (Exception e) { throw new CoreException(TracCorePlugin.toStatus(e, repository)); @@ -53,11 +54,24 @@ public class TracAttachmentHandler extends AbstractAttachmentHandler { } @Override - public void uploadAttachment(TaskRepository repository, ITask task, ITaskAttachment attachment, - String comment, IProgressMonitor monitor) throws CoreException { + public void postContent(TaskRepository repository, ITask task, AbstractTaskAttachmentSource source, String comment, + TaskAttribute attachmentAttribute, IProgressMonitor monitor) throws CoreException { if (!TracRepositoryConnector.hasAttachmentSupport(repository, task)) { - throw new CoreException(new RepositoryStatus(repository.getRepositoryUrl(), IStatus.INFO, TracCorePlugin.ID_PLUGIN, - RepositoryStatus.ERROR_REPOSITORY, "Attachments are not supported by this repository access type")); + throw new CoreException(new RepositoryStatus(repository.getRepositoryUrl(), IStatus.INFO, + TracCorePlugin.ID_PLUGIN, RepositoryStatus.ERROR_REPOSITORY, + "Attachments are not supported by this repository access type")); + } + + String filename = source.getName(); + String description = source.getDescription(); + if (attachmentAttribute != null) { + TaskAttachmentMapper mapper = TaskAttachmentMapper.createFrom(attachmentAttribute); + if (mapper.getFileName() != null) { + filename = mapper.getFileName(); + } + if (mapper.getDescription() != null) { + description = mapper.getDescription(); + } } monitor.beginTask("Uploading attachment", IProgressMonitor.UNKNOWN); @@ -65,8 +79,7 @@ public class TracAttachmentHandler extends AbstractAttachmentHandler { try { ITracClient client = connector.getClientManager().getTracClient(repository); int id = Integer.parseInt(task.getTaskId()); - client.putAttachmentData(id, attachment.getFilename(), attachment.getDescription(), - attachment.createInputStream(), monitor); + client.putAttachmentData(id, filename, description, source.createInputStream(monitor), monitor); if (comment != null && comment.length() > 0) { TracTicket ticket = new TracTicket(id); client.updateTicket(ticket, comment, monitor); @@ -80,29 +93,13 @@ public class TracAttachmentHandler extends AbstractAttachmentHandler { } @Override - public boolean canDownloadAttachment(TaskRepository repository, ITask task) { - if (repository == null) { - return false; - } + public boolean canGetContent(TaskRepository repository, ITask task) { return TracRepositoryConnector.hasAttachmentSupport(repository, task); } @Override - public boolean canUploadAttachment(TaskRepository repository, ITask task) { - if (repository == null) { - return false; - } + public boolean canPostContent(TaskRepository repository, ITask task) { return TracRepositoryConnector.hasAttachmentSupport(repository, task); } - @Override - public boolean canDeprecate(TaskRepository repository, RepositoryAttachment attachment) { - return false; - } - - @Override - public void updateAttachment(TaskRepository repository, RepositoryAttachment attachment) throws CoreException { - throw new UnsupportedOperationException(); - } - } diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttribute.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttribute.java new file mode 100644 index 000000000..cc305203d --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttribute.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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 + *******************************************************************************/ + +package org.eclipse.mylyn.internal.trac.core; + +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.mylyn.internal.trac.core.TracAttributeMapper.Flag; +import org.eclipse.mylyn.internal.trac.core.model.TracTicket.Key; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; + +/** + * @author Steffen Pingel + */ +public enum TracAttribute { + + CC(Key.CC, "CC:", TaskAttribute.USER_CC, TaskAttribute.TYPE_SHORT_TEXT, EnumSet.of(Flag.PEOPLE)), + + CHANGE_TIME(Key.CHANGE_TIME, "Last Modification:", TaskAttribute.DATE_MODIFICATION, TaskAttribute.TYPE_DATE, + EnumSet.of(Flag.READ_ONLY)), + + COMPONENT(Key.COMPONENT, "Component:", TaskAttribute.PRODUCT, TaskAttribute.TYPE_SINGLE_SELECT, + EnumSet.of(Flag.ATTRIBUTE)), + + DESCRIPTION(Key.DESCRIPTION, "Description:", TaskAttribute.DESCRIPTION, TaskAttribute.TYPE_LONG_RICH_TEXT), + + ID(Key.ID, "ID:", TaskAttribute.TASK_KEY, TaskAttribute.TYPE_SHORT_TEXT, EnumSet.of(Flag.PEOPLE)), + + KEYWORDS(Key.KEYWORDS, "Keywords:", TaskAttribute.KEYWORDS, TaskAttribute.TYPE_SHORT_TEXT, + EnumSet.of(Flag.ATTRIBUTE)), + + MILESTONE(Key.MILESTONE, "Milestone:", null, TaskAttribute.TYPE_SINGLE_SELECT, EnumSet.of(Flag.ATTRIBUTE)), + + OWNER(Key.OWNER, "Assigned to:", TaskAttribute.USER_ASSIGNED, TaskAttribute.TYPE_PERSON, EnumSet.of(Flag.PEOPLE)), + + PRIORITY(Key.PRIORITY, "Priority:", TaskAttribute.PRIORITY, TaskAttribute.TYPE_SINGLE_SELECT, + EnumSet.of(Flag.ATTRIBUTE)), + + REPORTER(Key.REPORTER, "Reporter:", TaskAttribute.USER_REPORTER, TaskAttribute.TYPE_PERSON, + EnumSet.of(Flag.READ_ONLY)), + + RESOLUTION(Key.RESOLUTION, "Resolution:", TaskAttribute.RESOLUTION, TaskAttribute.TYPE_SINGLE_SELECT), + + SEVERITY(Key.SEVERITY, "Severity:", null, TaskAttribute.TYPE_SINGLE_SELECT, EnumSet.of(Flag.ATTRIBUTE)), + + STATUS(Key.STATUS, "Status:", TaskAttribute.STATUS, TaskAttribute.TYPE_SHORT_TEXT), + + SUMMARY(Key.SUMMARY, "Summary:", TaskAttribute.SUMMARY, TaskAttribute.TYPE_SHORT_RICH_TEXT), + + TIME(Key.TIME, "Created:", TaskAttribute.DATE_CREATION, TaskAttribute.TYPE_DATE, EnumSet.of(Flag.READ_ONLY)), + + TYPE(Key.TYPE, "Type:", null, TaskAttribute.TYPE_SINGLE_SELECT, EnumSet.of(Flag.ATTRIBUTE)), + + VERSION(Key.VERSION, "Version:", null, TaskAttribute.TYPE_SINGLE_SELECT, EnumSet.of(Flag.ATTRIBUTE)); + + static Map<String, TracAttribute> attributeByTracKey = new HashMap<String, TracAttribute>(); + + static Map<String, String> tracKeyByTaskKey = new HashMap<String, String>(); + + private final String tracKey; + + private final String prettyName; + + private final String taskKey; + + private final String type; + + private EnumSet<Flag> flags; + + public static TracAttribute getByTaskKey(String taskKey) { + for (TracAttribute attribute : values()) { + if (taskKey.equals(attribute.getTaskKey())) { + return attribute; + } + } + return null; + } + + public static TracAttribute getByTracKey(String tracKey) { + for (TracAttribute attribute : values()) { + if (tracKey.equals(attribute.getTracKey())) { + return attribute; + } + } + return null; + } + + TracAttribute(Key tracKey, String prettyName, String taskKey, String type, EnumSet<Flag> flags) { + this.tracKey = tracKey.getKey(); + this.taskKey = taskKey; + this.prettyName = prettyName; + this.type = type; + this.flags = flags; + } + + TracAttribute(Key tracKey, String prettyName, String taskKey, String type) { + this(tracKey, prettyName, taskKey, type, TracAttributeMapper.NO_FLAGS); + } + + public String getTaskKey() { + return taskKey; + } + + public String getTracKey() { + return tracKey; + } + + public String getKind() { + if (flags.contains(Flag.ATTRIBUTE)) { + return TaskAttribute.KIND_DEFAULT; + } else if (flags.contains(Flag.PEOPLE)) { + return TaskAttribute.KIND_PEOPLE; + } + return null; + } + + public String getType() { + return type; + } + + public boolean isReadOnly() { + return flags.contains(Flag.READ_ONLY); + } + + @Override + public String toString() { + return prettyName; + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttributeFactory.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttributeFactory.java deleted file mode 100644 index ec6c59f4a..000000000 --- a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttributeFactory.java +++ /dev/null @@ -1,173 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2004, 2007 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 - *******************************************************************************/ - -package org.eclipse.mylyn.internal.trac.core; - -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import org.eclipse.mylyn.internal.tasks.core.deprecated.AbstractAttributeFactory; -import org.eclipse.mylyn.internal.tasks.core.deprecated.RepositoryTaskAttribute; -import org.eclipse.mylyn.internal.trac.core.model.TracTicket.Key; -import org.eclipse.mylyn.internal.trac.core.util.TracUtils; - -/** - * Provides a mapping from Mylyn task keys to Trac ticket keys. - * - * @author Steffen Pingel - */ -public class TracAttributeFactory extends AbstractAttributeFactory { - - private static final long serialVersionUID = 5333211422546115138L; - - private static Map<String, Attribute> attributeByTracKey = new HashMap<String, Attribute>(); - - private static Map<String, String> tracKeyByTaskKey = new HashMap<String, String>(); - - public enum Attribute { - CC(Key.CC, "CC:", RepositoryTaskAttribute.USER_CC, true, false), // - CHANGE_TIME(Key.CHANGE_TIME, "Last Modification:", RepositoryTaskAttribute.DATE_MODIFIED, true, true), // - COMPONENT(Key.COMPONENT, "Component:", RepositoryTaskAttribute.PRODUCT), // - DESCRIPTION(Key.DESCRIPTION, "Description:", RepositoryTaskAttribute.DESCRIPTION, true, false), // - ID(Key.ID, "<used by search engine>", null, true), // - KEYWORDS(Key.KEYWORDS, "Keywords:", RepositoryTaskAttribute.KEYWORDS), // - MILESTONE(Key.MILESTONE, "Milestone:", null), // - NEW_CC(RepositoryTaskAttribute.NEW_CC, "Add CC:"), // - OWNER(Key.OWNER, "Assigned to:", RepositoryTaskAttribute.USER_ASSIGNED, true, true), // - PRIORITY(Key.PRIORITY, "Priority:", null), // - REPORTER(Key.REPORTER, "Reporter:", RepositoryTaskAttribute.USER_REPORTER, true, true), // - RESOLUTION(Key.RESOLUTION, "Resolution:", RepositoryTaskAttribute.RESOLUTION, false, true), // - SEVERITY(Key.SEVERITY, "Severity:", null), // - STATUS(Key.STATUS, "Status:", RepositoryTaskAttribute.STATUS, false, true), // - SUMMARY(Key.SUMMARY, "Summary:", RepositoryTaskAttribute.SUMMARY, true), // - TIME(Key.TIME, "Created:", RepositoryTaskAttribute.DATE_CREATION, true, true), // - TYPE(Key.TYPE, "Type:", null), // - VERSION(Key.VERSION, "Version:", null); - - private final boolean isHidden; - - private final boolean isReadOnly; - - private final String tracKey; - - private final String prettyName; - - private final String taskKey; - - Attribute(String tracKey, String prettyName, String taskKey, boolean hidden, boolean readonly) { - this.tracKey = tracKey; - this.taskKey = taskKey; - this.prettyName = prettyName; - this.isHidden = hidden; - this.isReadOnly = readonly; - - attributeByTracKey.put(tracKey, this); - if (taskKey != null) { - tracKeyByTaskKey.put(taskKey, tracKey); - } - } - - Attribute(Key key, String prettyName, String taskKey, boolean hidden, boolean readonly) { - this(key.getKey(), prettyName, taskKey, hidden, readonly); - } - - Attribute(Key key, String prettyName, String taskKey, boolean hidden) { - this(key.getKey(), prettyName, taskKey, hidden, false); - } - - Attribute(Key key, String prettyName, String taskKey) { - this(key.getKey(), prettyName, taskKey, false, false); - } - - /** - * This is for Mylyn attributes that do not map to Trac attributes. - */ - Attribute(String taskKey, String prettyName) { - this(taskKey, prettyName, taskKey, true, false); - } - - public String getTaskKey() { - return taskKey; - } - - public String getTracKey() { - return tracKey; - } - - public boolean isHidden() { - return isHidden; - } - - public boolean isReadOnly() { - return isReadOnly; - } - - @Override - public String toString() { - return prettyName; - } - } - - static { - // make sure hash maps get initialized when class is loaded - Attribute.values(); - } - - @Override - public boolean isHidden(String key) { - if (isInternalAttribute(key)) { - return true; - } - - Attribute attribute = attributeByTracKey.get(key); - return (attribute != null) ? attribute.isHidden() : false; - } - - @Override - public String getName(String key) { - Attribute attribute = attributeByTracKey.get(key); - // TODO if attribute == null it is probably a custom field: need - // to query custom field information from repoository - return (attribute != null) ? attribute.toString() : key; - } - - @Override - public boolean isReadOnly(String key) { - Attribute attribute = attributeByTracKey.get(key); - return (attribute != null) ? attribute.isReadOnly() : false; - } - - @Override - public String mapCommonAttributeKey(String key) { - String tracKey = tracKeyByTaskKey.get(key); - return (tracKey != null) ? tracKey : key; - } - - static boolean isInternalAttribute(String id) { - return RepositoryTaskAttribute.COMMENT_NEW.equals(id) || RepositoryTaskAttribute.REMOVE_CC.equals(id) - || RepositoryTaskAttribute.NEW_CC.equals(id) || RepositoryTaskAttribute.ADD_SELF_CC.equals(id); - } - - @Override - public Date getDateForAttributeType(String attributeKey, String dateString) { - if (dateString == null || dateString.length() == 0) { - return null; - } - - try { - String mappedKey = mapCommonAttributeKey(attributeKey); - if (mappedKey.equals(Attribute.TIME.getTracKey()) || mappedKey.equals(Attribute.CHANGE_TIME.getTracKey())) { - return TracUtils.parseDate(Integer.valueOf(dateString)); - } - } catch (Exception e) { - } - return null; - } - -} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttributeMapper.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttributeMapper.java new file mode 100644 index 000000000..3c3184712 --- /dev/null +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracAttributeMapper.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 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 + *******************************************************************************/ + +package org.eclipse.mylyn.internal.trac.core; + +import java.util.Date; +import java.util.EnumSet; + +import org.eclipse.mylyn.internal.trac.core.util.TracUtils; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper; + +/** + * Provides a mapping from Mylyn task keys to Trac ticket keys. + * + * @author Steffen Pingel + */ +public class TracAttributeMapper extends TaskAttributeMapper { + + public enum Flag { + READ_ONLY, ATTRIBUTE, PEOPLE + }; + + public static final String NEW_CC = "task.common.newcc"; + + public static final String REMOVE_CC = "task.common.removecc"; + + public static final EnumSet<Flag> NO_FLAGS = EnumSet.noneOf(Flag.class); + + public static boolean isInternalAttribute(TaskAttribute attribute) { + if (TaskAttribute.TYPE_ATTACHMENT.equals(attribute.getMetaData().getType()) + || TaskAttribute.TYPE_OPERATION.equals(attribute.getMetaData().getType()) + || TaskAttribute.TYPE_COMMENT.equals(attribute.getMetaData().getType())) { + return true; + } + String id = attribute.getId(); + return TaskAttribute.COMMENT_NEW.equals(id) || TaskAttribute.ADD_SELF_CC.equals(id) || REMOVE_CC.equals(id) + || NEW_CC.equals(id); + } + +// +// @Override +// public boolean isHidden(String key) { +// if (isInternalAttribute(key)) { +// return true; +// } +// +// TracAttribute tracAttribute = attributeByTracKey.get(key); +// return (tracAttribute != null) ? tracAttribute.isHidden() : false; +// } +// +// @Override +// public String getName(String key) { +// TracAttribute tracAttribute = attributeByTracKey.get(key); +// // TODO if attribute == null it is probably a custom field: need +// // to query custom field information from repoository +// return (tracAttribute != null) ? tracAttribute.toString() : key; +// } +// +// @Override +// public boolean isReadOnly(String key) { +// TracAttribute tracAttribute = attributeByTracKey.get(key); +// return (tracAttribute != null) ? tracAttribute.isReadOnly() : false; +// } + + public TracAttributeMapper(TaskRepository taskRepository) { + super(taskRepository); + } + + @Override + public Date getDateValue(TaskAttribute attribute) { + return TracUtils.parseDate(Integer.valueOf(attribute.getValue())); + } + + @Override + public String mapToRepositoryKey(TaskAttribute parent, String key) { + TracAttribute attribute = TracAttribute.getByTaskKey(key); + return (attribute != null) ? attribute.getTracKey() : key; + } + + @Override + public void setDateValue(TaskAttribute attribute, Date date) { + attribute.setValue(TracUtils.toTracTime(date) + ""); + } + +} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracCorePlugin.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracCorePlugin.java index 47f772ca3..0bfd57a89 100644 --- a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracCorePlugin.java +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracCorePlugin.java @@ -13,6 +13,7 @@ import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Plugin; +import org.eclipse.mylyn.internal.trac.core.util.TracUtils; import org.eclipse.mylyn.tasks.core.RepositoryStatus; import org.eclipse.mylyn.tasks.core.TaskRepository; import org.osgi.framework.BundleContext; @@ -30,7 +31,7 @@ public class TracCorePlugin extends Plugin { private static TracCorePlugin plugin; - public final static String REPOSITORY_KIND = "trac"; + public final static String CONNECTOR_KIND = "trac"; private TracRepositoryConnector connector; @@ -79,17 +80,17 @@ public class TracCorePlugin extends Plugin { if (e instanceof TracLoginException) { return RepositoryStatus.createLoginError(repository.getRepositoryUrl(), ID_PLUGIN); } else if (e instanceof TracPermissionDeniedException) { - return TracStatus.createPermissionDeniedError(repository.getRepositoryUrl(), ID_PLUGIN); + return TracUtils.createPermissionDeniedError(repository.getRepositoryUrl(), ID_PLUGIN); } else if (e instanceof InvalidTicketException) { - return new RepositoryStatus(repository.getRepositoryUrl(), IStatus.ERROR, ID_PLUGIN, RepositoryStatus.ERROR_IO, - "The server returned an unexpected response", e); + return new RepositoryStatus(repository.getRepositoryUrl(), IStatus.ERROR, ID_PLUGIN, + RepositoryStatus.ERROR_IO, "The server returned an unexpected response", e); } else if (e instanceof TracException) { String message = e.getMessage(); if (message == null) { message = "I/O error has occured"; } - return new RepositoryStatus(repository.getRepositoryUrl(), IStatus.ERROR, ID_PLUGIN, RepositoryStatus.ERROR_IO, - message, e); + return new RepositoryStatus(repository.getRepositoryUrl(), IStatus.ERROR, ID_PLUGIN, + RepositoryStatus.ERROR_IO, message, e); } else if (e instanceof ClassCastException) { return new RepositoryStatus(IStatus.ERROR, ID_PLUGIN, RepositoryStatus.ERROR_IO, "Unexpected server response: " + e.getMessage(), e); diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracRepositoryConnector.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracRepositoryConnector.java index ec39c9a27..010db0431 100644 --- a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracRepositoryConnector.java +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracRepositoryConnector.java @@ -10,9 +10,12 @@ package org.eclipse.mylyn.internal.trac.core; import java.io.File; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Set; +import java.util.StringTokenizer; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; @@ -23,50 +26,298 @@ import org.eclipse.core.runtime.Status; import org.eclipse.mylyn.commons.net.AuthenticationCredentials; import org.eclipse.mylyn.commons.net.AuthenticationType; import org.eclipse.mylyn.commons.net.Policy; -import org.eclipse.mylyn.internal.tasks.core.AbstractTask; -import org.eclipse.mylyn.internal.tasks.core.deprecated.AbstractAttachmentHandler; -import org.eclipse.mylyn.internal.tasks.core.deprecated.AbstractLegacyRepositoryConnector; -import org.eclipse.mylyn.internal.tasks.core.deprecated.AbstractTaskDataHandler; -import org.eclipse.mylyn.internal.tasks.core.deprecated.LegacyTaskDataCollector; -import org.eclipse.mylyn.internal.tasks.core.deprecated.RepositoryOperation; -import org.eclipse.mylyn.internal.tasks.core.deprecated.RepositoryTaskAttribute; -import org.eclipse.mylyn.internal.tasks.core.deprecated.RepositoryTaskData; import org.eclipse.mylyn.internal.trac.core.ITracClient.Version; -import org.eclipse.mylyn.internal.trac.core.TracAttributeFactory.Attribute; -import org.eclipse.mylyn.internal.trac.core.TracTask.Kind; import org.eclipse.mylyn.internal.trac.core.model.TracPriority; +import org.eclipse.mylyn.internal.trac.core.model.TracSearch; import org.eclipse.mylyn.internal.trac.core.model.TracTicket; -import org.eclipse.mylyn.internal.trac.core.model.TracTicket.Key; import org.eclipse.mylyn.internal.trac.core.util.TracUtils; +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.ITaskMapping; import org.eclipse.mylyn.tasks.core.RepositoryStatus; import org.eclipse.mylyn.tasks.core.TaskRepository; import org.eclipse.mylyn.tasks.core.TaskRepositoryLocationFactory; +import org.eclipse.mylyn.tasks.core.ITask.PriorityLevel; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskAttachmentHandler; +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.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; /** * @author Steffen Pingel */ -public class TracRepositoryConnector extends AbstractLegacyRepositoryConnector { +public class TracRepositoryConnector extends AbstractRepositoryConnector { + + public enum TaskKind { + DEFECT, ENHANCEMENT, TASK; + + public static TaskKind fromString(String type) { + if (type == null) { + return null; + } + if (type.equals("Defect")) { + return DEFECT; + } + if (type.equals("Enhancement")) { + return ENHANCEMENT; + } + if (type.equals("Task")) { + return TASK; + } + return null; + } + + public static TaskKind fromType(String type) { + if (type == null) { + return null; + } + if (type.equals("defect")) { + return DEFECT; + } + if (type.equals("enhancement")) { + return ENHANCEMENT; + } + if (type.equals("task")) { + return TASK; + } + return null; + } + + @Override + public String toString() { + switch (this) { + case DEFECT: + return "Defect"; + case ENHANCEMENT: + return "Enhancement"; + case TASK: + return "Task"; + default: + return ""; + } + } + + } + + public enum TaskStatus { + ASSIGNED, CLOSED, NEW, REOPENED; + + public static TaskStatus fromStatus(String status) { + if (status == null) { + return null; + } + if (status.equals("new")) { + return NEW; + } + if (status.equals("assigned")) { + return ASSIGNED; + } + if (status.equals("reopened")) { + return REOPENED; + } + if (status.equals("closed")) { + return CLOSED; + } + return null; + } + + public String toStatusString() { + switch (this) { + case NEW: + return "new"; + case ASSIGNED: + return "assigned"; + case REOPENED: + return "reopened"; + case CLOSED: + return "closed"; + default: + return ""; + } + } + + @Override + public String toString() { + switch (this) { + case NEW: + return "New"; + case ASSIGNED: + return "Assigned"; + case REOPENED: + return "Reopened"; + case CLOSED: + return "Closed"; + default: + return ""; + } + } + + } + + public enum TracPriorityLevel { + BLOCKER, CRITICAL, MAJOR, MINOR, TRIVIAL; + + public static TracPriorityLevel fromPriority(String priority) { + if (priority == null) { + return null; + } + if (priority.equals("blocker")) { + return BLOCKER; + } + if (priority.equals("critical")) { + return CRITICAL; + } + if (priority.equals("major")) { + return MAJOR; + } + if (priority.equals("minor")) { + return MINOR; + } + if (priority.equals("trivial")) { + return TRIVIAL; + } + return null; + } + + public PriorityLevel toPriorityLevel() { + switch (this) { + case BLOCKER: + return PriorityLevel.P1; + case CRITICAL: + return PriorityLevel.P2; + case MAJOR: + return PriorityLevel.P3; + case MINOR: + return PriorityLevel.P4; + case TRIVIAL: + return PriorityLevel.P5; + default: + return null; + } + } + + @Override + public String toString() { + switch (this) { + case BLOCKER: + return "blocker"; + case CRITICAL: + return "critical"; + case MAJOR: + return "major"; + case MINOR: + return "minor"; + case TRIVIAL: + return "trivial"; + default: + return null; + } + } + } private final static String CLIENT_LABEL = "Trac (supports 0.9 or 0.10 through Web and XML-RPC)"; - private TracClientManager clientManager; + private static int TASK_PRIORITY_LEVELS = 5; - private final TracTaskDataHandler taskDataHandler = new TracTaskDataHandler(this); + public static final String TASK_KEY_SUPPORTS_SUBTASKS = "SupportsSubtasks"; + + public static String getDisplayUsername(TaskRepository repository) { + AuthenticationCredentials credentials = repository.getCredentials(AuthenticationType.REPOSITORY); + if (credentials != null && credentials.getUserName().length() > 0) { + return ITracClient.DEFAULT_USERNAME; + } + return repository.getUserName(); + } + + public static PriorityLevel getTaskPriority(String tracPriority) { + if (tracPriority != null) { + TracPriorityLevel priority = TracPriorityLevel.fromPriority(tracPriority); + if (priority != null) { + return priority.toPriorityLevel(); + } + } + return PriorityLevel.getDefault(); + } + + public static PriorityLevel getTaskPriority(String priority, TracPriority[] tracPriorities) { + if (priority != null && tracPriorities != null && tracPriorities.length > 0) { + int minValue = tracPriorities[0].getValue(); + int range = tracPriorities[tracPriorities.length - 1].getValue() - minValue; + for (TracPriority tracPriority : tracPriorities) { + if (priority.equals(tracPriority.getName())) { + float relativeValue = (float) (tracPriority.getValue() - minValue) / range; + int value = (int) (relativeValue * TASK_PRIORITY_LEVELS) + 1; + return PriorityLevel.fromLevel(value); + } + } + } + return getTaskPriority(priority); + } + + public static int getTicketId(String taskId) throws CoreException { + try { + return Integer.parseInt(taskId); + } catch (NumberFormatException e) { + throw new CoreException(new Status(IStatus.ERROR, TracCorePlugin.ID_PLUGIN, IStatus.OK, + "Invalid ticket id: " + taskId, e)); + } + } + + static List<String> getAttributeValues(TaskData data, String attributeId) { + TaskAttribute attribute = data.getRoot().getMappedAttribute(attributeId); + if (attribute != null) { + return attribute.getValues(); + } else { + return Collections.emptyList(); + } + } + + static String getAttributeValue(TaskData data, String attributeId) { + TaskAttribute attribute = data.getRoot().getMappedAttribute(attributeId); + if (attribute != null) { + return attribute.getValue(); + } else { + return ""; + } + } + + public static boolean hasAttachmentSupport(TaskRepository repository, ITask task) { + return Version.XML_RPC.name().equals(repository.getVersion()); + } + + public static boolean hasChangedSince(TaskRepository repository) { + return Version.XML_RPC.name().equals(repository.getVersion()); + } + + public static boolean hasRichEditor(TaskRepository repository) { + return Version.XML_RPC.name().equals(repository.getVersion()); + } + + public static boolean hasRichEditor(TaskRepository repository, ITask task) { + return hasRichEditor(repository); + } + + public static boolean isCompleted(String tracStatus) { + TaskStatus taskStatus = TaskStatus.fromStatus(tracStatus); + return taskStatus == TaskStatus.CLOSED; + } private final TracAttachmentHandler attachmentHandler = new TracAttachmentHandler(this); - private TaskRepositoryLocationFactory taskRepositoryLocationFactory = new TaskRepositoryLocationFactory(); + private TracClientManager clientManager; private File repositoryConfigurationCacheFile; - public TracRepositoryConnector(File repositoryConfigurationCacheFile) { - this.repositoryConfigurationCacheFile = repositoryConfigurationCacheFile; + private final TracTaskDataHandler taskDataHandler = new TracTaskDataHandler(this); - } + private TaskRepositoryLocationFactory taskRepositoryLocationFactory = new TaskRepositoryLocationFactory(); + + private final TracWikiHandler wikiHandler = new TracWikiHandler(this); public TracRepositoryConnector() { if (TracCorePlugin.getDefault() != null) { @@ -76,6 +327,11 @@ public class TracRepositoryConnector extends AbstractLegacyRepositoryConnector { } } + public TracRepositoryConnector(File repositoryConfigurationCacheFile) { + this.repositoryConfigurationCacheFile = repositoryConfigurationCacheFile; + + } + @Override public boolean canCreateNewTask(TaskRepository repository) { return true; @@ -86,19 +342,21 @@ public class TracRepositoryConnector extends AbstractLegacyRepositoryConnector { return true; } - private final TracWikiHandler wikiHandler = new TracWikiHandler(this); + @Override + public AbstractTaskAttachmentHandler getTaskAttachmentHandler() { + return attachmentHandler; + } - public boolean hasWiki(TaskRepository repository) { - // check the access mode to validate Wiki support - ITracClient client = getClientManager().getTracClient(repository); - if (client instanceof ITracWikiClient) { - return true; + public synchronized TracClientManager getClientManager() { + if (clientManager == null) { + clientManager = new TracClientManager(repositoryConfigurationCacheFile, taskRepositoryLocationFactory); } - return false; + return clientManager; } - public AbstractWikiHandler getWikiHandler() { - return wikiHandler; + @Override + public String getConnectorKind() { + return TracCorePlugin.CONNECTOR_KIND; } @Override @@ -107,8 +365,14 @@ public class TracRepositoryConnector extends AbstractLegacyRepositoryConnector { } @Override - public String getConnectorKind() { - return TracCorePlugin.REPOSITORY_KIND; + public TaskData getTaskData(TaskRepository repository, String taskId, IProgressMonitor monitor) + throws CoreException { + return taskDataHandler.getTaskData(repository, taskId, monitor); + } + + @Override + public AbstractTaskDataHandler getTaskDataHandler() { + return taskDataHandler; } @Override @@ -130,18 +394,30 @@ public class TracRepositoryConnector extends AbstractLegacyRepositoryConnector { } @Override + public String getTaskIdPrefix() { + return "#"; + } + + public TaskRepositoryLocationFactory getTaskRepositoryLocationFactory() { + return taskRepositoryLocationFactory; + } + + @Override public String getTaskUrl(String repositoryUrl, String taskId) { return repositoryUrl + ITracClient.TICKET_URL + taskId; } - @Override - public AbstractAttachmentHandler getAttachmentHandler() { - return attachmentHandler; + public AbstractWikiHandler getWikiHandler() { + return wikiHandler; } - @Override - public AbstractTaskDataHandler getLegacyTaskDataHandler() { - return taskDataHandler; + public boolean hasWiki(TaskRepository repository) { + // check the access mode to validate Wiki support + ITracClient client = getClientManager().getTracClient(repository); + if (client instanceof ITracWikiClient) { + return true; + } + return false; } @Override @@ -150,20 +426,22 @@ public class TracRepositoryConnector extends AbstractLegacyRepositoryConnector { try { monitor.beginTask("Querying repository", IProgressMonitor.UNKNOWN); - final List<TracTicket> tickets = new ArrayList<TracTicket>(); + TracSearch search = TracUtils.toTracSearch(query); + if (search == null) { + return new RepositoryStatus(repository.getRepositoryUrl(), IStatus.ERROR, TracCorePlugin.ID_PLUGIN, + RepositoryStatus.ERROR_REPOSITORY, "The query is invalid: \"" + query.getUrl() + "\""); + } ITracClient client; try { client = getClientManager().getTracClient(repository); - if (query instanceof TracRepositoryQuery) { - client.search(((TracRepositoryQuery) query).getTracSearch(), tickets, monitor); - } + final List<TracTicket> tickets = new ArrayList<TracTicket>(); + client.search(search, tickets, monitor); client.updateAttributes(monitor, false); for (TracTicket ticket : tickets) { - RepositoryTaskData taskData = taskDataHandler.createTaskDataFromTicket(client, repository, ticket, - monitor); - ((LegacyTaskDataCollector) resultCollector).accept(taskData); + TaskData taskData = taskDataHandler.createTaskDataFromTicket(client, repository, ticket, monitor); + resultCollector.accept(taskData); } } catch (Throwable e) { return TracCorePlugin.toStatus(e, repository); @@ -176,6 +454,34 @@ public class TracRepositoryConnector extends AbstractLegacyRepositoryConnector { } @Override + public void postSynchronization(ISynchronizationSession event, IProgressMonitor monitor) throws CoreException { + try { + monitor.beginTask("", 1); + if (event.isFullSynchronization()) { + Date date = getSynchronizationTimestamp(event); + if (date != null) { + event.getTaskRepository().setSynchronizationTimeStamp(TracUtils.toTracTime(date) + ""); + } + } + } finally { + monitor.done(); + } + } + + private Date getSynchronizationTimestamp(ISynchronizationSession event) { + Date mostRecent = new Date(0); + Date mostRecentTimeStamp = TracUtils.parseDate(event.getTaskRepository().getSynchronizationTimeStamp()); + for (ITask task : event.getChangedTasks()) { + Date taskModifiedDate = task.getModificationDate(); + if (taskModifiedDate != null && taskModifiedDate.after(mostRecent)) { + mostRecent = taskModifiedDate; + mostRecentTimeStamp = task.getModificationDate(); + } + } + return mostRecentTimeStamp; + } + + @Override public void preSynchronization(ISynchronizationSession session, IProgressMonitor monitor) throws CoreException { monitor = Policy.monitorFor(monitor); try { @@ -250,70 +556,12 @@ public class TracRepositoryConnector extends AbstractLegacyRepositoryConnector { } } - @Override - public AbstractTask createTask(String repositoryUrl, String id, String summary) { - TracTask tracTask = new TracTask(repositoryUrl, id, summary); - tracTask.setCreationDate(new Date()); - return tracTask; - } - - @Override - public boolean updateTaskFromTaskData(TaskRepository taskRepository, ITask task, RepositoryTaskData taskData) { - TracTask tracTask = (TracTask) task; - ITracClient client = getClientManager().getTracClient(taskRepository); - - task.setSummary(taskData.getSummary()); - task.setOwner(taskData.getAttributeValue(RepositoryTaskAttribute.USER_ASSIGNED)); - if (TracTask.isCompleted(taskData.getStatus())) { - task.setCompletionDate(TracUtils.parseDate(Integer.valueOf(taskData.getLastModified()))); - } else { - task.setCompletionDate(null); - } - task.setUrl(taskRepository.getRepositoryUrl() + ITracClient.TICKET_URL + taskData.getTaskId()); - - String priority = taskData.getAttributeValue(Attribute.PRIORITY.getTracKey()); - TracPriority[] tracPriorities = client.getPriorities(); - task.setPriority(TracTask.getTaskPriority(priority, tracPriorities).toString()); - - Kind kind = TracTask.Kind.fromType(taskData.getAttributeValue(Attribute.TYPE.getTracKey())); - task.setTaskKind((kind != null) ? kind.toString() : null); - - tracTask.setSupportsSubtasks(taskDataHandler.canInitializeSubTaskData(null, taskData)); - - // TODO check return value - return false; - } - - public static int getTicketId(String taskId) throws CoreException { - try { - return Integer.parseInt(taskId); - } catch (NumberFormatException e) { - throw new CoreException(new Status(IStatus.ERROR, TracCorePlugin.ID_PLUGIN, IStatus.OK, - "Invalid ticket id: " + taskId, e)); - } - } - - public synchronized TracClientManager getClientManager() { - if (clientManager == null) { - clientManager = new TracClientManager(repositoryConfigurationCacheFile, taskRepositoryLocationFactory); + public synchronized void setTaskRepositoryLocationFactory( + TaskRepositoryLocationFactory taskRepositoryLocationFactory) { + this.taskRepositoryLocationFactory = taskRepositoryLocationFactory; + if (this.clientManager != null) { + clientManager.setTaskRepositoryLocationFactory(taskRepositoryLocationFactory); } - return clientManager; - } - - public static boolean hasChangedSince(TaskRepository repository) { - return Version.XML_RPC.name().equals(repository.getVersion()); - } - - public static boolean hasRichEditor(TaskRepository repository) { - return Version.XML_RPC.name().equals(repository.getVersion()); - } - - public static boolean hasRichEditor(TaskRepository repository, ITask task) { - return hasRichEditor(repository); - } - - public static boolean hasAttachmentSupport(TaskRepository repository, ITask task) { - return Version.XML_RPC.name().equals(repository.getVersion()); } public void stop() { @@ -333,110 +581,73 @@ public class TracRepositoryConnector extends AbstractLegacyRepositoryConnector { } } - public static String getDisplayUsername(TaskRepository repository) { - AuthenticationCredentials credentials = repository.getCredentials(AuthenticationType.REPOSITORY); - if (credentials != null && credentials.getUserName().length() > 0) { - return ITracClient.DEFAULT_USERNAME; + @Override + public void updateTaskFromTaskData(TaskRepository taskRepository, ITask task, TaskData taskData) { + TaskMapper mapper = getTaskMapper(taskRepository, taskData); + mapper.applyTo(task); + if (isCompleted(mapper.getStatus())) { + task.setCompletionDate(mapper.getModificationDate()); + } else { + task.setCompletionDate(null); } - return repository.getUserName(); + task.setUrl(taskRepository.getRepositoryUrl() + ITracClient.TICKET_URL + taskData.getTaskId()); + task.setAttribute(TASK_KEY_SUPPORTS_SUBTASKS, Boolean.toString(taskDataHandler.supportsSubtasks(taskData))); } @Override - public String getTaskIdPrefix() { - return "#"; - } - - public static TracTicket getTracTicket(TaskRepository repository, RepositoryTaskData data) - throws InvalidTicketException, CoreException { - TracTicket ticket = new TracTicket(getTicketId(data.getTaskId())); - - List<RepositoryTaskAttribute> attributes = data.getAttributes(); - for (RepositoryTaskAttribute attribute : attributes) { - if (TracAttributeFactory.isInternalAttribute(attribute.getId())) { - // ignore - } else if (!attribute.isReadOnly()) { - ticket.putValue(attribute.getId(), attribute.getValue()); - } - } - - // set cc value - StringBuilder sb = new StringBuilder(); - List<String> removeValues = data.getAttributeValues(RepositoryTaskAttribute.REMOVE_CC); - List<String> values = data.getAttributeValues(RepositoryTaskAttribute.USER_CC); - for (String user : values) { - if (!removeValues.contains(user)) { - if (sb.length() > 0) { - sb.append(","); - } - sb.append(user); - } - } - if (data.getAttributeValue(RepositoryTaskAttribute.NEW_CC).length() > 0) { - if (sb.length() > 0) { - sb.append(","); - } - sb.append(data.getAttributeValue(RepositoryTaskAttribute.NEW_CC)); - } - if (RepositoryTaskAttribute.TRUE.equals(data.getAttributeValue(RepositoryTaskAttribute.ADD_SELF_CC))) { - if (sb.length() > 0) { - sb.append(","); - } - sb.append(repository.getUserName()); - } - ticket.putBuiltinValue(Key.CC, sb.toString()); - - RepositoryOperation operation = data.getSelectedOperation(); - if (operation != null) { - String action = operation.getKnobName(); - if (!"leave".equals(action)) { - if ("accept".equals(action)) { - ticket.putValue("status", TracTask.Status.ASSIGNED.toStatusString()); - ticket.putValue("owner", getDisplayUsername(repository)); - } else if ("resolve".equals(action)) { - ticket.putValue("status", TracTask.Status.CLOSED.toStatusString()); - ticket.putValue("resolution", operation.getOptionSelection()); - } else if ("reopen".equals(action)) { - ticket.putValue("status", TracTask.Status.REOPENED.toStatusString()); - ticket.putValue("resolution", ""); - } else if ("reassign".equals(operation.getKnobName())) { - ticket.putValue("status", TracTask.Status.NEW.toStatusString()); - ticket.putValue("owner", operation.getInputValue()); - } + public boolean hasTaskChanged(TaskRepository taskRepository, ITask task, TaskData taskData) { + TaskMapper mapper = getTaskMapper(taskRepository, taskData); + if (taskData.isPartial()) { + return mapper.hasChanges(task); + } else { + Date repositoryDate = mapper.getModificationDate(); + Date localDate = task.getModificationDate(); + if (repositoryDate != null && repositoryDate.equals(localDate)) { + return false; } + return true; } - - return ticket; } - public TaskRepositoryLocationFactory getTaskRepositoryLocationFactory() { - return taskRepositoryLocationFactory; - } - - public synchronized void setTaskRepositoryLocationFactory( - TaskRepositoryLocationFactory taskRepositoryLocationFactory) { - this.taskRepositoryLocationFactory = taskRepositoryLocationFactory; - if (this.clientManager != null) { - clientManager.setTaskRepositoryLocationFactory(taskRepositoryLocationFactory); + @Override + public Collection<TaskRelation> getTaskRelations(TaskData taskData) { + TaskAttribute attribute = taskData.getRoot().getAttribute(TracTaskDataHandler.ATTRIBUTE_BLOCKED_BY); + if (attribute != null) { + List<TaskRelation> result = new ArrayList<TaskRelation>(); + StringTokenizer t = new StringTokenizer(attribute.getValue(), ", "); + while (t.hasMoreTokens()) { + result.add(TaskRelation.subtask(t.nextToken())); + } + return result; } + return Collections.emptySet(); } @Override - public RepositoryTaskData getLegacyTaskData(TaskRepository repository, String taskId, IProgressMonitor monitor) - throws CoreException { - return getLegacyTaskDataHandler().getTaskData(repository, taskId, monitor); - } + public ITaskMapping getTaskMapping(TaskData taskData) { + return getTaskMapper(null, taskData); + } + + public TaskMapper getTaskMapper(TaskRepository taskRepository, TaskData taskData) { + final ITracClient client = (taskRepository != null) ? getClientManager().getTracClient(taskRepository) : null; + return new TaskMapper(taskData) { + @Override + public PriorityLevel getPriorityLevel() { + if (client != null) { + String priority = getPriority(); + TracPriority[] tracPriorities = client.getPriorities(); + return getTaskPriority(priority, tracPriorities); + } + return null; + } - @Override - public void postSynchronization(ISynchronizationSession event, IProgressMonitor monitor) throws CoreException { - try { - monitor.beginTask("", 1); - if (event.isFullSynchronization()) { - event.getTaskRepository().setSynchronizationTimeStamp( - getSynchronizationTimestamp(event.getTaskRepository(), event.getChangedTasks())); + @Override + public String getTaskKind() { + TaskKind taskKind = TaskKind.fromType(super.getTaskKind()); + return (taskKind != null) ? taskKind.toString() : null; } - } finally { - monitor.done(); - } + + }; } }
\ No newline at end of file diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracRepositoryQuery.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracRepositoryQuery.java deleted file mode 100644 index 5744bd599..000000000 --- a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracRepositoryQuery.java +++ /dev/null @@ -1,54 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2004, 2007 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 - *******************************************************************************/ - -package org.eclipse.mylyn.internal.trac.core; - -import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery; -import org.eclipse.mylyn.internal.trac.core.model.TracSearch; - -/** - * @author Steffen Pingel - */ -public class TracRepositoryQuery extends RepositoryQuery { - - public TracRepositoryQuery(String repositoryUrl, String queryUrl, String description) { - super(description); - - assert queryUrl.startsWith(repositoryUrl + ITracClient.QUERY_URL); - - setRepositoryUrl(repositoryUrl); - setUrl(queryUrl); - } - - @Override - public String getConnectorKind() { - return TracCorePlugin.REPOSITORY_KIND; - } - - public String getQueryParameter() { - String url = getUrl(); - int i = url.indexOf(ITracClient.QUERY_URL); - if (i == -1) { - return null; - } - return url.substring(i + ITracClient.QUERY_URL.length()); - } - - /** - * Creates a <code>TracSearch</code> object from this query. - */ - public TracSearch getTracSearch() { - TracSearch search = new TracSearch(); - String url = getQueryParameter(); - if (url != null) { - search.fromUrl(url); - } - return search; - } - -} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracStatus.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracStatus.java deleted file mode 100644 index 555a6ad2b..000000000 --- a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracStatus.java +++ /dev/null @@ -1,24 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2004, 2007 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 - *******************************************************************************/ - -package org.eclipse.mylyn.internal.trac.core; - -import org.eclipse.core.runtime.IStatus; -import org.eclipse.mylyn.tasks.core.RepositoryStatus; - -/** - * @author Steffen Pingel - */ -public class TracStatus { - - public static IStatus createPermissionDeniedError(String repositoryUrl, String pluginId) { - return new RepositoryStatus(repositoryUrl, IStatus.ERROR, TracCorePlugin.ID_PLUGIN, - RepositoryStatus.ERROR_PERMISSION_DENIED, "Permission denied."); - } - -} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracTask.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracTask.java deleted file mode 100644 index 30d94f878..000000000 --- a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracTask.java +++ /dev/null @@ -1,244 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2004, 2007 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 - *******************************************************************************/ - -package org.eclipse.mylyn.internal.trac.core; - -import org.eclipse.mylyn.internal.tasks.core.AbstractTask; -import org.eclipse.mylyn.internal.trac.core.model.TracPriority; - -/** - * @author Steffen Pingel - */ -public class TracTask extends AbstractTask { - - public enum Kind { - DEFECT, ENHANCEMENT, TASK; - - public static Kind fromString(String type) { - if (type == null) { - return null; - } - if (type.equals("Defect")) { - return DEFECT; - } - if (type.equals("Enhancement")) { - return ENHANCEMENT; - } - if (type.equals("Task")) { - return TASK; - } - return null; - } - - public static Kind fromType(String type) { - if (type == null) { - return null; - } - if (type.equals("defect")) { - return DEFECT; - } - if (type.equals("enhancement")) { - return ENHANCEMENT; - } - if (type.equals("task")) { - return TASK; - } - return null; - } - - @Override - public String toString() { - switch (this) { - case DEFECT: - return "Defect"; - case ENHANCEMENT: - return "Enhancement"; - case TASK: - return "Task"; - default: - return ""; - } - } - - } - - public enum Status { - ASSIGNED, CLOSED, NEW, REOPENED; - - public static Status fromStatus(String status) { - if (status == null) { - return null; - } - if (status.equals("new")) { - return NEW; - } - if (status.equals("assigned")) { - return ASSIGNED; - } - if (status.equals("reopened")) { - return REOPENED; - } - if (status.equals("closed")) { - return CLOSED; - } - return null; - } - - public String toStatusString() { - switch (this) { - case NEW: - return "new"; - case ASSIGNED: - return "assigned"; - case REOPENED: - return "reopened"; - case CLOSED: - return "closed"; - default: - return ""; - } - } - - @Override - public String toString() { - switch (this) { - case NEW: - return "New"; - case ASSIGNED: - return "Assigned"; - case REOPENED: - return "Reopened"; - case CLOSED: - return "Closed"; - default: - return ""; - } - } - - } - - public enum TracPriorityLevel { - BLOCKER, CRITICAL, MAJOR, MINOR, TRIVIAL; - - public static TracPriorityLevel fromPriority(String priority) { - if (priority == null) { - return null; - } - if (priority.equals("blocker")) { - return BLOCKER; - } - if (priority.equals("critical")) { - return CRITICAL; - } - if (priority.equals("major")) { - return MAJOR; - } - if (priority.equals("minor")) { - return MINOR; - } - if (priority.equals("trivial")) { - return TRIVIAL; - } - return null; - } - - public PriorityLevel toPriorityLevel() { - switch (this) { - case BLOCKER: - return PriorityLevel.P1; - case CRITICAL: - return PriorityLevel.P2; - case MAJOR: - return PriorityLevel.P3; - case MINOR: - return PriorityLevel.P4; - case TRIVIAL: - return PriorityLevel.P5; - default: - return null; - } - } - - @Override - public String toString() { - switch (this) { - case BLOCKER: - return "blocker"; - case CRITICAL: - return "critical"; - case MAJOR: - return "major"; - case MINOR: - return "minor"; - case TRIVIAL: - return "trivial"; - default: - return null; - } - } - - } - - private static int TASK_PRIORITY_LEVELS = 5; - - public static PriorityLevel getTaskPriority(String tracPriority) { - if (tracPriority != null) { - TracPriorityLevel priority = TracPriorityLevel.fromPriority(tracPriority); - if (priority != null) { - return priority.toPriorityLevel(); - } - } - return PriorityLevel.getDefault(); - } - - public static PriorityLevel getTaskPriority(String priority, TracPriority[] tracPriorities) { - if (priority != null && tracPriorities != null && tracPriorities.length > 0) { - int minValue = tracPriorities[0].getValue(); - int range = tracPriorities[tracPriorities.length - 1].getValue() - minValue; - for (TracPriority tracPriority : tracPriorities) { - if (priority.equals(tracPriority.getName())) { - float relativeValue = (float) (tracPriority.getValue() - minValue) / range; - int value = (int) (relativeValue * TASK_PRIORITY_LEVELS) + 1; - return PriorityLevel.fromLevel(value); - } - } - } - return getTaskPriority(priority); - } - - public static boolean isCompleted(String tracStatus) { - TracTask.Status status = TracTask.Status.fromStatus(tracStatus); - return status == TracTask.Status.CLOSED; - } - - private boolean supportsSubtasks = false; - - public TracTask(String repositoryUrl, String id, String label) { - super(repositoryUrl, id, label); - setUrl(repositoryUrl + ITracClient.TICKET_URL + id); - } - - @Override - public String getConnectorKind() { - return TracCorePlugin.REPOSITORY_KIND; - } - - @Override - public boolean isLocal() { - return false; - } - - public boolean getSupportsSubtasks() { - return supportsSubtasks; - } - - public void setSupportsSubtasks(boolean supportsSubtasks) { - this.supportsSubtasks = supportsSubtasks; - } - -} diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracTaskDataHandler.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracTaskDataHandler.java index bad39705b..81c2afaf6 100644 --- a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracTaskDataHandler.java +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/TracTaskDataHandler.java @@ -10,9 +10,8 @@ package org.eclipse.mylyn.internal.trac.core; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; +import java.util.Collection; import java.util.Date; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -23,26 +22,26 @@ import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.mylyn.commons.net.Policy; -import org.eclipse.mylyn.internal.tasks.core.deprecated.AbstractAttributeFactory; -import org.eclipse.mylyn.internal.tasks.core.deprecated.AbstractTaskDataHandler; -import org.eclipse.mylyn.internal.tasks.core.deprecated.DefaultTaskSchema; -import org.eclipse.mylyn.internal.tasks.core.deprecated.RepositoryAttachment; -import org.eclipse.mylyn.internal.tasks.core.deprecated.RepositoryOperation; -import org.eclipse.mylyn.internal.tasks.core.deprecated.RepositoryTaskAttribute; -import org.eclipse.mylyn.internal.tasks.core.deprecated.RepositoryTaskData; -import org.eclipse.mylyn.internal.tasks.core.deprecated.TaskComment; -import org.eclipse.mylyn.internal.trac.core.TracAttributeFactory.Attribute; -import org.eclipse.mylyn.internal.trac.core.TracTask.Kind; import org.eclipse.mylyn.internal.trac.core.model.TracAttachment; import org.eclipse.mylyn.internal.trac.core.model.TracComment; -import org.eclipse.mylyn.internal.trac.core.model.TracPriority; import org.eclipse.mylyn.internal.trac.core.model.TracTicket; import org.eclipse.mylyn.internal.trac.core.model.TracTicketField; import org.eclipse.mylyn.internal.trac.core.model.TracTicket.Key; import org.eclipse.mylyn.internal.trac.core.util.TracUtils; 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.RepositoryStatus; import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.RepositoryResponse.ResponseKind; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler; +import org.eclipse.mylyn.tasks.core.data.TaskAttachmentMapper; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper; +import org.eclipse.mylyn.tasks.core.data.TaskCommentMapper; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tasks.core.data.TaskMapper; +import org.eclipse.mylyn.tasks.core.data.TaskOperation; /** * @author Steffen Pingel @@ -55,16 +54,13 @@ public class TracTaskDataHandler extends AbstractTaskDataHandler { private static final String CC_DELIMETER = ", "; - private final AbstractAttributeFactory attributeFactory = new TracAttributeFactory(); - private final TracRepositoryConnector connector; public TracTaskDataHandler(TracRepositoryConnector connector) { this.connector = connector; } - @Override - public RepositoryTaskData getTaskData(TaskRepository repository, String taskId, IProgressMonitor monitor) + public TaskData getTaskData(TaskRepository repository, String taskId, IProgressMonitor monitor) throws CoreException { monitor = Policy.monitorFor(monitor); try { @@ -75,7 +71,7 @@ public class TracTaskDataHandler extends AbstractTaskDataHandler { } } - public RepositoryTaskData downloadTaskData(TaskRepository repository, int taskId, IProgressMonitor monitor) + public TaskData downloadTaskData(TaskRepository repository, int taskId, IProgressMonitor monitor) throws CoreException { ITracClient client = connector.getClientManager().getTracClient(repository); TracTicket ticket; @@ -91,17 +87,19 @@ public class TracTaskDataHandler extends AbstractTaskDataHandler { return createTaskDataFromTicket(client, repository, ticket, monitor); } - public RepositoryTaskData createTaskDataFromTicket(ITracClient client, TaskRepository repository, - TracTicket ticket, IProgressMonitor monitor) throws CoreException { - RepositoryTaskData taskData = new RepositoryTaskData(attributeFactory, TracCorePlugin.REPOSITORY_KIND, + public TaskData createTaskDataFromTicket(ITracClient client, TaskRepository repository, TracTicket ticket, + IProgressMonitor monitor) throws CoreException { + TaskData taskData = new TaskData(getAttributeMapper(repository), TracCorePlugin.CONNECTOR_KIND, repository.getRepositoryUrl(), ticket.getId() + ""); try { if (!TracRepositoryConnector.hasRichEditor(repository)) { - updateTaskDataFromTicket(taskData, ticket, client); + createDefaultAttributes(taskData, client, true); + updateTaskData(repository, taskData, ticket); + //updateTaskDataFromTicket(taskData, ticket, client); taskData.setPartial(true); } else { - createDefaultAttributes(attributeFactory, taskData, client, true); - updateTaskData(repository, attributeFactory, taskData, ticket); + createDefaultAttributes(taskData, client, true); + updateTaskData(repository, taskData, ticket); } return taskData; } catch (OperationCanceledException e) { @@ -112,21 +110,10 @@ public class TracTaskDataHandler extends AbstractTaskDataHandler { } } - @Override - public AbstractAttributeFactory getAttributeFactory(String repositoryUrl, String repositoryKind, String taskKind) { - // we don't care about the repository information right now - return attributeFactory; - } - - @Override - public AbstractAttributeFactory getAttributeFactory(RepositoryTaskData taskData) { - return getAttributeFactory(taskData.getRepositoryUrl(), taskData.getConnectorKind(), taskData.getTaskKind()); - } - - public static void updateTaskData(TaskRepository repository, AbstractAttributeFactory factory, - RepositoryTaskData data, TracTicket ticket) { + public static void updateTaskData(TaskRepository repository, TaskData data, TracTicket ticket) { if (ticket.getCreated() != null) { - data.setAttributeValue(Attribute.TIME.getTracKey(), TracUtils.toTracTime(ticket.getCreated()) + ""); + data.getRoot().getAttribute(TracAttribute.TIME.getTracKey()).setValue( + TracUtils.toTracTime(ticket.getCreated()) + ""); } Date lastChanged = ticket.getLastChanged(); @@ -136,53 +123,53 @@ public class TracTaskDataHandler extends AbstractTaskDataHandler { if (Key.CC.getKey().equals(key)) { StringTokenizer t = new StringTokenizer(valueByKey.get(key), CC_DELIMETER); while (t.hasMoreTokens()) { - data.addAttributeValue(key, t.nextToken()); + data.getRoot().getAttribute(key).addValue(t.nextToken()); } } else { - data.setAttributeValue(key, valueByKey.get(key)); + data.getRoot().getAttribute(key).setValue(valueByKey.get(key)); } } TracComment[] comments = ticket.getComments(); if (comments != null) { + int count = 1; for (int i = 0; i < comments.length; i++) { if (!"comment".equals(comments[i].getField()) || "".equals(comments[i].getNewValue())) { continue; } - TaskComment taskComment = new TaskComment(factory, data.getComments().size() + 1); - taskComment.setAttributeValue(RepositoryTaskAttribute.COMMENT_AUTHOR, comments[i].getAuthor()); - taskComment.setAttributeValue(RepositoryTaskAttribute.COMMENT_DATE, comments[i].getCreated().toString()); - taskComment.setAttributeValue(RepositoryTaskAttribute.COMMENT_TEXT, comments[i].getNewValue()); - data.addComment(taskComment); + TaskCommentMapper mapper = new TaskCommentMapper(); + mapper.setAuthor(repository.createPerson(comments[i].getAuthor())); + mapper.setCreationDate(comments[i].getCreated()); + mapper.setText(comments[i].getNewValue()); + mapper.setNumber(count); + + TaskAttribute attribute = data.getRoot().createAttribute(TaskAttribute.PREFIX_COMMENT + count); + mapper.applyTo(attribute); + count++; } } TracAttachment[] attachments = ticket.getAttachments(); if (attachments != null) { for (int i = 0; i < attachments.length; i++) { - RepositoryAttachment taskAttachment = new RepositoryAttachment(factory); - taskAttachment.setCreator(attachments[i].getAuthor()); - taskAttachment.setRepositoryKind(TracCorePlugin.REPOSITORY_KIND); - taskAttachment.setRepositoryUrl(repository.getRepositoryUrl()); - taskAttachment.setTaskId("" + ticket.getId()); - taskAttachment.setAttributeValue(Attribute.DESCRIPTION.getTracKey(), attachments[i].getDescription()); - taskAttachment.setAttributeValue(RepositoryTaskAttribute.ATTACHMENT_FILENAME, - attachments[i].getFilename()); - taskAttachment.setAttributeValue(RepositoryTaskAttribute.ATTACHMENT_SIZE, attachments[i].getSize() + ""); - taskAttachment.setAttributeValue(RepositoryTaskAttribute.USER_OWNER, attachments[i].getAuthor()); + TaskAttachmentMapper mapper = new TaskAttachmentMapper(); + mapper.setAuthor(repository.createPerson(attachments[i].getAuthor())); + mapper.setDescription(attachments[i].getDescription()); + mapper.setFileName(attachments[i].getFilename()); + mapper.setLength((long) attachments[i].getSize()); if (attachments[i].getCreated() != null) { if (lastChanged == null || attachments[i].getCreated().after(lastChanged)) { lastChanged = attachments[i].getCreated(); } - - taskAttachment.setAttributeValue(RepositoryTaskAttribute.ATTACHMENT_DATE, - attachments[i].getCreated().toString()); + mapper.setCreationDate(attachments[i].getCreated()); } - taskAttachment.setAttributeValue(RepositoryTaskAttribute.ATTACHMENT_URL, repository.getRepositoryUrl() - + ITracClient.TICKET_ATTACHMENT_URL + ticket.getId() + "/" + attachments[i].getFilename()); - taskAttachment.setAttributeValue(RepositoryTaskAttribute.ATTACHMENT_ID, i + ""); - data.addAttachment(taskAttachment); + mapper.setUrl(repository.getRepositoryUrl() + ITracClient.TICKET_ATTACHMENT_URL + ticket.getId() + "/" + + attachments[i].getFilename()); + mapper.setAttachmentId(i + ""); + + TaskAttribute attribute = data.getRoot().createAttribute(TaskAttribute.PREFIX_ATTACHMENT + (i + 1)); + mapper.applyTo(attribute); } } @@ -193,82 +180,62 @@ public class TracTaskDataHandler extends AbstractTaskDataHandler { addOperation(repository, data, ticket, actionList, "leave"); addOperation(repository, data, ticket, actionList, "accept"); addOperation(repository, data, ticket, actionList, "resolve"); - addOperation(repository, data, ticket, actionList, "reassign"); addOperation(repository, data, ticket, actionList, "reopen"); } if (lastChanged != null) { - data.setAttributeValue(Attribute.CHANGE_TIME.getTracKey(), TracUtils.toTracTime(lastChanged) + ""); + data.getRoot().getAttribute(TracAttribute.CHANGE_TIME.getTracKey()).setValue( + TracUtils.toTracTime(lastChanged) + ""); } } // TODO Reuse Labels from BugzillaServerFacade - private static void addOperation(TaskRepository repository, RepositoryTaskData data, TracTicket ticket, - List<String> actions, String action) { + private static void addOperation(TaskRepository repository, TaskData data, TracTicket ticket, List<String> actions, + String action) { if (!actions.remove(action)) { return; } - RepositoryOperation operation = null; + String label = null; if ("leave".equals(action)) { - operation = new RepositoryOperation(action, "Leave as " + data.getStatus() + " " + data.getResolution()); - operation.setChecked(true); + // TODO + //label = "Leave as " + data.getStatus() + " " + data.getResolution(); + label = "Leave"; } else if ("accept".equals(action)) { - operation = new RepositoryOperation(action, "Accept"); + label = "Accept"; } else if ("resolve".equals(action)) { - operation = new RepositoryOperation(action, "Resolve as"); - operation.setUpOptions("resolution"); - for (String resolution : ticket.getResolutions()) { - operation.addOption(resolution, resolution); - } - } else if ("reassign".equals(action)) { - operation = new RepositoryOperation(action, "Reassign to"); - operation.setInputName("owner"); - operation.setInputValue(TracRepositoryConnector.getDisplayUsername(repository)); + label = "Resolve as"; } else if ("reopen".equals(action)) { - operation = new RepositoryOperation(action, "Reopen"); + label = "Reopen"; } - if (operation != null) { - data.addOperation(operation); + if (label != null) { + TaskAttribute attribute = data.getRoot().createAttribute(TaskAttribute.PREFIX_OPERATION + action); + TaskOperation.applyTo(attribute, action, label); + if ("resolve".equals(action)) { + attribute.getMetaData().putValue(TaskAttribute.META_ASSOCIATED_ATTRIBUTE_ID, + TracAttribute.RESOLUTION.getTracKey()); + } } } - public static void createDefaultAttributes(AbstractAttributeFactory factory, RepositoryTaskData data, - ITracClient client, boolean existingTask) { - TracTicketField[] fields = client.getTicketFields(); - - if (existingTask) { - createAttribute(factory, data, Attribute.STATUS, client.getTicketStatus()); - createAttribute(factory, data, Attribute.RESOLUTION, client.getTicketResolutions()); - } - - createAttribute(factory, data, Attribute.COMPONENT, client.getComponents()); - createAttribute(factory, data, Attribute.VERSION, client.getVersions(), true); - createAttribute(factory, data, Attribute.PRIORITY, client.getPriorities()); - createAttribute(factory, data, Attribute.SEVERITY, client.getSeverities()); - - createAttribute(factory, data, Attribute.TYPE, client.getTicketTypes()); - RepositoryTaskAttribute attribute = createAttribute(factory, data, Attribute.OWNER); - if (!existingTask) { - attribute.setReadOnly(false); - } - createAttribute(factory, data, Attribute.MILESTONE, client.getMilestones(), true); + public static void createDefaultAttributes(TaskData data, ITracClient client, boolean existingTask) { + createAttribute(data, TracAttribute.SUMMARY); + createAttribute(data, TracAttribute.DESCRIPTION); if (existingTask) { - createAttribute(factory, data, Attribute.REPORTER); + createAttribute(data, TracAttribute.TIME); + createAttribute(data, TracAttribute.CHANGE_TIME); + createAttribute(data, TracAttribute.STATUS, client.getTicketStatus()); + createAttribute(data, TracAttribute.RESOLUTION, client.getTicketResolutions()); } - - if (existingTask) { - createAttribute(factory, data, Attribute.NEW_CC); - } - createAttribute(factory, data, Attribute.CC); - createAttribute(factory, data, Attribute.KEYWORDS); - - if (!existingTask) { - createAttribute(factory, data, Attribute.SUMMARY); - createAttribute(factory, data, Attribute.DESCRIPTION); - } - + createAttribute(data, TracAttribute.COMPONENT, client.getComponents()); + createAttribute(data, TracAttribute.VERSION, client.getVersions(), true); + createAttribute(data, TracAttribute.PRIORITY, client.getPriorities()); + createAttribute(data, TracAttribute.SEVERITY, client.getSeverities()); + createAttribute(data, TracAttribute.MILESTONE, client.getMilestones(), true); + createAttribute(data, TracAttribute.TYPE, client.getTicketTypes()); + createAttribute(data, TracAttribute.KEYWORDS); + TracTicketField[] fields = client.getTicketFields(); if (fields != null) { for (TracTicketField field : fields) { if (field.isCustom()) { @@ -276,29 +243,46 @@ public class TracTaskDataHandler extends AbstractTaskDataHandler { } } } + // people + createAttribute(data, TracAttribute.OWNER); + if (existingTask) { + createAttribute(data, TracAttribute.REPORTER); + } + createAttribute(data, TracAttribute.CC); + if (existingTask) { + data.getRoot().createAttribute(TracAttributeMapper.NEW_CC).getMetaData().setType( + TaskAttribute.TYPE_SHORT_TEXT).setReadOnly(false); + data.getRoot().createAttribute(TracAttributeMapper.REMOVE_CC); + data.getRoot().createAttribute(TaskAttribute.COMMENT_NEW).getMetaData().setType( + TaskAttribute.TYPE_LONG_RICH_TEXT).setReadOnly(false); + } + // operations + data.getRoot().createAttribute(TaskAttribute.OPERATION).getMetaData().setType(TaskAttribute.TYPE_OPERATION); } - private static void createAttribute(RepositoryTaskData data, TracTicketField field) { - RepositoryTaskAttribute attr = new RepositoryTaskAttribute(field.getName(), field.getLabel(), false); + private static void createAttribute(TaskData data, TracTicketField field) { + TaskAttribute attr = data.getRoot().createAttribute(field.getName()); + attr.getMetaData().setLabel(field.getLabel()); + attr.getMetaData().setKind(TaskAttribute.KIND_DEFAULT); if (field.getType() == TracTicketField.Type.CHECKBOX) { // attr.addOption("True", "1"); // attr.addOption("False", "0"); - attr.addOption("1", "1"); - attr.addOption("0", "0"); - + attr.getMetaData().setType(TaskAttribute.TYPE_BOOLEAN); + attr.putOption("1", "1"); + attr.putOption("0", "0"); if (field.getDefaultValue() != null) { attr.setValue(field.getDefaultValue()); } } else if (field.getType() == TracTicketField.Type.SELECT || field.getType() == TracTicketField.Type.RADIO) { + attr.getMetaData().setType(TaskAttribute.TYPE_SINGLE_SELECT); String[] values = field.getOptions(); if (values != null && values.length > 0) { if (field.isOptional()) { - attr.addOption("", ""); + attr.putOption("", ""); } for (String value : values) { - attr.addOption(value, value); + attr.putOption(value, value); } - if (field.getDefaultValue() != null) { try { int index = Integer.parseInt(field.getDefaultValue()); @@ -320,51 +304,54 @@ public class TracTaskDataHandler extends AbstractTaskDataHandler { attr.setValue(field.getDefaultValue()); } } - data.addAttribute(attr.getId(), attr); } - private static RepositoryTaskAttribute createAttribute(AbstractAttributeFactory factory, RepositoryTaskData data, - Attribute attribute, Object[] values, boolean allowEmtpy) { - RepositoryTaskAttribute attr = factory.createAttribute(attribute.getTracKey()); + private static TaskAttribute createAttribute(TaskData data, TracAttribute tracAttribute) { + TaskAttribute attr = data.getRoot().createAttribute(tracAttribute.getTracKey()); + attr.getMetaData().setType(tracAttribute.getType()); + attr.getMetaData().setKind(tracAttribute.getKind()); + attr.getMetaData().setLabel(tracAttribute.toString()); + attr.getMetaData().setReadOnly(tracAttribute.isReadOnly()); + return attr; + } + + private static TaskAttribute createAttribute(TaskData data, TracAttribute tracAttribute, Object[] values, + boolean allowEmtpy) { + TaskAttribute attr = createAttribute(data, tracAttribute); if (values != null && values.length > 0) { if (allowEmtpy) { - attr.addOption("", ""); + attr.putOption("", ""); } for (Object value : values) { - attr.addOption(value.toString(), value.toString()); + attr.putOption(value.toString(), value.toString()); } } else { - attr.setHidden(true); - attr.setReadOnly(true); + attr.getMetaData().setReadOnly(true); } - data.addAttribute(attribute.getTracKey(), attr); - return attr; - } - - private static RepositoryTaskAttribute createAttribute(AbstractAttributeFactory factory, RepositoryTaskData data, - Attribute attribute) { - RepositoryTaskAttribute attr = factory.createAttribute(attribute.getTracKey()); - data.addAttribute(attribute.getTracKey(), attr); return attr; } - private static RepositoryTaskAttribute createAttribute(AbstractAttributeFactory factory, RepositoryTaskData data, - Attribute attribute, Object[] values) { - return createAttribute(factory, data, attribute, values, false); + private static TaskAttribute createAttribute(TaskData data, TracAttribute tracAttribute, Object[] values) { + return createAttribute(data, tracAttribute, values, false); } @Override - public String postTaskData(TaskRepository repository, RepositoryTaskData taskData, IProgressMonitor monitor) - throws CoreException { + public RepositoryResponse postTaskData(TaskRepository repository, TaskData taskData, + Set<TaskAttribute> oldAttributes, IProgressMonitor monitor) throws CoreException { try { - TracTicket ticket = TracRepositoryConnector.getTracTicket(repository, taskData); + TracTicket ticket = TracTaskDataHandler.getTracTicket(repository, taskData); ITracClient server = connector.getClientManager().getTracClient(repository); if (taskData.isNew()) { int id = server.createTicket(ticket, monitor); - return id + ""; + return new RepositoryResponse(ResponseKind.TASK_CREATED, id + ""); } else { - server.updateTicket(ticket, taskData.getNewComment(), monitor); - return null; + String newComment = ""; + TaskAttribute newCommentAttribute = taskData.getRoot().getMappedAttribute(TaskAttribute.COMMENT_NEW); + if (newCommentAttribute != null) { + newComment = newCommentAttribute.getValue(); + } + server.updateTicket(ticket, newComment, monitor); + return new RepositoryResponse(ResponseKind.TASK_UPDATED, ticket.getId() + ""); } } catch (OperationCanceledException e) { throw e; @@ -375,12 +362,12 @@ public class TracTaskDataHandler extends AbstractTaskDataHandler { } @Override - public boolean initializeTaskData(TaskRepository repository, RepositoryTaskData data, IProgressMonitor monitor) - throws CoreException { + public boolean initializeTaskData(TaskRepository repository, TaskData data, ITaskMapping initializationData, + IProgressMonitor monitor) throws CoreException { try { ITracClient client = connector.getClientManager().getTracClient(repository); client.updateAttributes(monitor, false); - createDefaultAttributes(attributeFactory, data, client, false); + createDefaultAttributes(data, client, false); return true; } catch (OperationCanceledException e) { throw e; @@ -391,77 +378,163 @@ public class TracTaskDataHandler extends AbstractTaskDataHandler { } @Override - public boolean initializeSubTaskData(TaskRepository repository, RepositoryTaskData taskData, - RepositoryTaskData parentTaskData, IProgressMonitor monitor) throws CoreException { - initializeTaskData(repository, taskData, monitor); - RepositoryTaskAttribute attribute = taskData.getAttribute(ATTRIBUTE_BLOCKING); + public boolean initializeSubTaskData(TaskRepository repository, TaskData taskData, TaskData parentTaskData, + IProgressMonitor monitor) throws CoreException { + initializeTaskData(repository, taskData, null, monitor); + TaskAttribute attribute = taskData.getRoot().getMappedAttribute(ATTRIBUTE_BLOCKING); if (attribute == null) { throw new CoreException(new RepositoryStatus(repository, IStatus.ERROR, TracCorePlugin.ID_PLUGIN, RepositoryStatus.ERROR_REPOSITORY, "The repository does not support subtasks")); } - cloneTaskData(parentTaskData, taskData); - taskData.setDescription(""); - taskData.setSummary(""); + + TaskMapper mapper = new TaskMapper(taskData); + mapper.merge(new TaskMapper(parentTaskData)); + mapper.setDescription(""); + mapper.setSummary(""); attribute.setValue(parentTaskData.getTaskId()); return true; } @Override - public Set<String> getSubTaskIds(RepositoryTaskData taskData) { - RepositoryTaskAttribute attribute = taskData.getAttribute(ATTRIBUTE_BLOCKED_BY); - if (attribute != null) { - Set<String> result = new HashSet<String>(); - StringTokenizer t = new StringTokenizer(attribute.getValue(), ", "); - while (t.hasMoreTokens()) { - result.add(t.nextToken()); - } - return result; - } - return Collections.emptySet(); + public boolean canInitializeSubTaskData(TaskRepository taskRepository, ITask task) { + return Boolean.parseBoolean(task.getAttribute(TracRepositoryConnector.TASK_KEY_SUPPORTS_SUBTASKS)); } +// /** +// * Updates attributes of <code>taskData</code> from <code>ticket</code>. +// */ +// public void updateTaskDataFromTicket(TaskData taskData, TracTicket ticket, ITracClient client) { +// DefaultTaskSchema schema = new DefaultTaskSchema(taskData); +// if (ticket.getValue(Key.SUMMARY) != null) { +// schema.setSummary(ticket.getValue(Key.SUMMARY)); +// } +// +// if (TracRepositoryConnector.isCompleted(ticket.getValue(Key.STATUS))) { +// schema.setCompletionDate(ticket.getLastChanged()); +// } else { +// schema.setCompletionDate(null); +// } +// +// String priority = ticket.getValue(Key.PRIORITY); +// TracPriority[] tracPriorities = client.getPriorities(); +// schema.setPriority(TracRepositoryConnector.getTaskPriority(priority, tracPriorities)); +// +// if (ticket.getValue(Key.TYPE) != null) { +// TaskKind taskKind = TracRepositoryConnector.TaskKind.fromType(ticket.getValue(Key.TYPE)); +// schema.setTaskKind((taskKind != null) ? taskKind.toString() : ticket.getValue(Key.TYPE)); +// } +// +// if (ticket.getCreated() != null) { +// schema.setCreationDate(ticket.getCreated()); +// } +// +// if (ticket.getCustomValue(TracTaskDataHandler.ATTRIBUTE_BLOCKING) != null) { +// taskData.addAttribute(ATTRIBUTE_BLOCKED_BY, new TaskAttribute(ATTRIBUTE_BLOCKED_BY, "Blocked by", true)); +// } +// } + @Override - public boolean canInitializeSubTaskData(ITask task, RepositoryTaskData parentTaskData) { - if (parentTaskData != null) { - return parentTaskData.getAttribute(ATTRIBUTE_BLOCKED_BY) != null; - } else if (task instanceof TracTask) { - return ((TracTask) task).getSupportsSubtasks(); - } - return false; + public TaskAttributeMapper getAttributeMapper(TaskRepository taskRepository) { + return new TracAttributeMapper(taskRepository); } - /** - * Updates attributes of <code>taskData</code> from <code>ticket</code>. - */ - public void updateTaskDataFromTicket(RepositoryTaskData taskData, TracTicket ticket, ITracClient client) { - DefaultTaskSchema schema = new DefaultTaskSchema(taskData); - if (ticket.getValue(Key.SUMMARY) != null) { - schema.setSummary(ticket.getValue(Key.SUMMARY)); + public boolean supportsSubtasks(TaskData taskData) { + return taskData.getRoot().getAttribute(ATTRIBUTE_BLOCKING) != null; + } + + public static TracTicket getTracTicket(TaskRepository repository, TaskData data) throws InvalidTicketException, + CoreException { + TracTicket ticket = (data.isNew()) ? new TracTicket() : new TracTicket( + TracRepositoryConnector.getTicketId(data.getTaskId())); + + Collection<TaskAttribute> attributes = data.getRoot().getAttributes().values(); + for (TaskAttribute attribute : attributes) { + if (TracAttributeMapper.isInternalAttribute(attribute)) { + // ignore + } else if (!attribute.getMetaData().isReadOnly()) { + ticket.putValue(attribute.getId(), attribute.getValue()); + } } - if (TracTask.isCompleted(ticket.getValue(Key.STATUS))) { - schema.setCompletionDate(ticket.getLastChanged()); - } else { - schema.setCompletionDate(null); + // set cc value + StringBuilder sb = new StringBuilder(); + List<String> removeValues = TracRepositoryConnector.getAttributeValues(data, TracAttributeMapper.REMOVE_CC); + List<String> values = TracRepositoryConnector.getAttributeValues(data, TaskAttribute.USER_CC); + for (String user : values) { + if (!removeValues.contains(user)) { + if (sb.length() > 0) { + sb.append(","); + } + sb.append(user); + } + } + if (TracRepositoryConnector.getAttributeValue(data, TracAttributeMapper.NEW_CC).length() > 0) { + if (sb.length() > 0) { + sb.append(","); + } + sb.append(TracRepositoryConnector.getAttributeValue(data, TracAttributeMapper.NEW_CC)); + } + if (Boolean.TRUE.equals(TracRepositoryConnector.getAttributeValue(data, TaskAttribute.ADD_SELF_CC))) { + if (sb.length() > 0) { + sb.append(","); + } + sb.append(repository.getUserName()); + } + ticket.putBuiltinValue(Key.CC, sb.toString()); + + ticket.putValue("owner", TracRepositoryConnector.getAttributeValue(data, TaskAttribute.USER_ASSIGNED)); + + TaskAttribute operationAttribute = data.getRoot().getMappedAttribute(TaskAttribute.OPERATION); + if (operationAttribute != null) { + TaskOperation operation = TaskOperation.createFrom(operationAttribute); + String action = operation.getOperationId(); + if (!"leave".equals(action)) { + if ("accept".equals(action)) { + ticket.putValue("status", TracRepositoryConnector.TaskStatus.ASSIGNED.toStatusString()); + } else if ("resolve".equals(action)) { + ticket.putValue("status", TracRepositoryConnector.TaskStatus.CLOSED.toStatusString()); + ticket.putValue("resolution", TracRepositoryConnector.getAttributeValue(data, + TaskAttribute.RESOLUTION)); + } else if ("reopen".equals(action)) { + ticket.putValue("status", TracRepositoryConnector.TaskStatus.REOPENED.toStatusString()); + ticket.putValue("resolution", ""); + } else if ("reassign".equals(action)) { + ticket.putValue("status", TracRepositoryConnector.TaskStatus.NEW.toStatusString()); + } + } } - String priority = ticket.getValue(Key.PRIORITY); - TracPriority[] tracPriorities = client.getPriorities(); - schema.setPriority(TracTask.getTaskPriority(priority, tracPriorities)); + return ticket; + } - if (ticket.getValue(Key.TYPE) != null) { - Kind kind = TracTask.Kind.fromType(ticket.getValue(Key.TYPE)); - schema.setTaskKind((kind != null) ? kind.toString() : ticket.getValue(Key.TYPE)); + @Override + public void migrateTaskData(TaskRepository taskRepository, TaskData taskData) { + int version = 0; + try { + version = Integer.parseInt(taskData.getVersion()); + } catch (NumberFormatException e) { + // ignore } - if (ticket.getCreated() != null) { - schema.setCreationDate(ticket.getCreated()); + if (version < 1) { + Map<String, TaskAttribute> attributes = taskData.getRoot().getAttributes(); + for (TaskAttribute attribute : attributes.values()) { + if (TaskAttribute.OPERATION.equals(attribute.getId())) { + attribute.getMetaData().setType(TaskAttribute.TYPE_OPERATION); + } else if (TracAttributeMapper.NEW_CC.equals(attribute.getId())) { + attribute.getMetaData().setType(TaskAttribute.TYPE_SHORT_TEXT).setReadOnly(false); + } else { + TracAttribute tracAttribute = TracAttribute.getByTracKey(attribute.getId()); + if (tracAttribute != null) { + attribute.getMetaData().setType(tracAttribute.getType()); + attribute.getMetaData().setKind(tracAttribute.getKind()); + attribute.getMetaData().setReadOnly(tracAttribute.isReadOnly()); + } + } + } } - if (ticket.getCustomValue(TracTaskDataHandler.ATTRIBUTE_BLOCKING) != null) { - taskData.addAttribute(ATTRIBUTE_BLOCKED_BY, new RepositoryTaskAttribute(ATTRIBUTE_BLOCKED_BY, "Blocked by", - true)); - } + taskData.setVersion("1"); } } diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracComponent.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracComponent.java index 55aa33015..327cadb13 100644 --- a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracComponent.java +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracComponent.java @@ -11,7 +11,7 @@ package org.eclipse.mylyn.internal.trac.core.model; /** * @author Steffen Pingel */ -public class TracComponent extends TracAttribute { +public class TracComponent extends TracRepositoryAttribute { private static final long serialVersionUID = -6181067219323677076L; diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracMilestone.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracMilestone.java index 0b1ba7065..978989f94 100644 --- a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracMilestone.java +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracMilestone.java @@ -14,7 +14,7 @@ import java.util.Date; /** * @author Steffen Pingel */ -public class TracMilestone extends TracAttribute implements Serializable { +public class TracMilestone extends TracRepositoryAttribute implements Serializable { private static final long serialVersionUID = 6648558552508886484L; diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracAttribute.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracRepositoryAttribute.java index ed74bf8a7..a820d935d 100644 --- a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracAttribute.java +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracRepositoryAttribute.java @@ -10,16 +10,19 @@ package org.eclipse.mylyn.internal.trac.core.model; import java.io.Serializable; +import org.eclipse.core.runtime.Assert; + /** * @author Steffen Pingel */ -public class TracAttribute implements Serializable { +public class TracRepositoryAttribute implements Serializable { private static final long serialVersionUID = -4535033208999685315L; private String name; - public TracAttribute(String name) { + public TracRepositoryAttribute(String name) { + Assert.isNotNull(name); this.name = name; } @@ -33,7 +36,8 @@ public class TracAttribute implements Serializable { @Override public String toString() { - return name; + // FIXME serialization can restore null values here + return (name != null) ? name : ""; } } diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracVersion.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracVersion.java index 0b4665fa1..5f5d5c2e3 100644 --- a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracVersion.java +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/model/TracVersion.java @@ -13,7 +13,7 @@ import java.util.Date; /** * @author Steffen Pingel */ -public class TracVersion extends TracAttribute { +public class TracVersion extends TracRepositoryAttribute { private static final long serialVersionUID = 9018237956062697410L; diff --git a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/util/TracUtils.java b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/util/TracUtils.java index dfd105712..b4c9093e6 100644 --- a/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/util/TracUtils.java +++ b/org.eclipse.mylyn.trac.core/src/org/eclipse/mylyn/internal/trac/core/util/TracUtils.java @@ -10,6 +10,13 @@ package org.eclipse.mylyn.internal.trac.core.util; import java.util.Date; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.mylyn.internal.trac.core.ITracClient; +import org.eclipse.mylyn.internal.trac.core.TracCorePlugin; +import org.eclipse.mylyn.internal.trac.core.model.TracSearch; +import org.eclipse.mylyn.tasks.core.IRepositoryQuery; +import org.eclipse.mylyn.tasks.core.RepositoryStatus; + /** * Provides static helper methods. * @@ -17,6 +24,16 @@ import java.util.Date; */ public class TracUtils { + public static Date parseDate(String time) { + if (time != null) { + try { + return TracUtils.parseDate(Long.valueOf(time)); + } catch (NumberFormatException e) { + } + } + return null; + } + public static Date parseDate(long seconds) { return new Date(seconds * 1000l); // Calendar c = Calendar.getInstance(); @@ -33,4 +50,31 @@ public class TracUtils { return date.getTime() / 1000l; } + private static String getQueryParameter(IRepositoryQuery query) { + String url = query.getUrl(); + int i = url.indexOf(ITracClient.QUERY_URL); + if (i == -1) { + return null; + } + return url.substring(i + ITracClient.QUERY_URL.length()); + } + + /** + * Creates a <code>TracSearch</code> object from this query. + */ + public static TracSearch toTracSearch(IRepositoryQuery query) { + String url = getQueryParameter(query); + if (url != null) { + TracSearch search = new TracSearch(); + search.fromUrl(url); + return search; + } + return null; + } + + public static IStatus createPermissionDeniedError(String repositoryUrl, String pluginId) { + return new RepositoryStatus(repositoryUrl, IStatus.ERROR, TracCorePlugin.ID_PLUGIN, + RepositoryStatus.ERROR_PERMISSION_DENIED, "Permission denied."); + } + } |