/******************************************************************************* * Copyright (c) 2004, 2010 Tasktop Technologies and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Tasktop Technologies - initial API and implementation *******************************************************************************/ package org.eclipse.mylyn.internal.bugzilla.core; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import org.eclipse.mylyn.tasks.core.IRepositoryPerson; import org.eclipse.mylyn.tasks.core.TaskRepository; 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.TaskDataCollector; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * Parser for xml bugzilla reports. * * @author Rob Elves * @author Hiroyuki Inaba (internationalization) */ public class SaxMultiBugReportContentHandler extends DefaultHandler { private static final String ATTRIBUTE_NAME = "name"; //$NON-NLS-1$ private static final String ID_STRING_BEGIN = " (id="; //$NON-NLS-1$ private static final String ID_STRING_END = ")"; //$NON-NLS-1$ private StringBuffer characters; private TaskComment taskComment; private Map<String, TaskCommentMapper> attachIdToComment = new HashMap<String, TaskCommentMapper>(); private int commentNum = 0; private BugzillaAttachmentMapper attachment; private final Map<String, TaskData> taskDataMap; private TaskData repositoryTaskData; private List<TaskComment> longDescs; private String errorMessage = null; private final List<BugzillaCustomField> customFields; private final TaskDataCollector collector; private boolean isDeprecated = false; private boolean isPatch = false; private String token; private String exporter; private TaskAttribute attachmentAttribute; private boolean bugParseErrorOccurred; private final BugzillaRepositoryConnector connector; private boolean useIsPrivate; private final TaskAttributeMapper mapper; private final SimpleDateFormat simpleFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm"); //$NON-NLS-1$ private final SimpleDateFormat simpleFormatter_deltaTS = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //$NON-NLS-1$ public SaxMultiBugReportContentHandler(TaskAttributeMapper mapper, TaskDataCollector collector, Map<String, TaskData> taskDataMap, List<BugzillaCustomField> customFields, BugzillaRepositoryConnector connector) { this.mapper = mapper; this.taskDataMap = taskDataMap; this.customFields = customFields; this.collector = collector; this.connector = connector; TaskRepository taskRepository = mapper.getTaskRepository(); String useParam = taskRepository.getProperty(IBugzillaConstants.BUGZILLA_INSIDER_GROUP); useIsPrivate = (useParam == null || (useParam != null && useParam.equals("true"))); //$NON-NLS-1$ } public boolean errorOccurred() { return errorMessage != null; } public String getErrorMessage() { return errorMessage; } @Override public void characters(char[] ch, int start, int length) throws SAXException { characters.append(ch, start, length); //System.err.println(String.copyValueOf(ch, start, length)); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { characters = new StringBuffer(); BugzillaAttribute tag = BugzillaAttribute.UNKNOWN; if (localName.startsWith(BugzillaCustomField.CUSTOM_FIELD_PREFIX)) { return; } try { tag = BugzillaAttribute.valueOf(localName.trim().toUpperCase(Locale.ENGLISH)); } catch (RuntimeException e) { if (e instanceof IllegalArgumentException) { // ignore unrecognized tags return; } throw e; } switch (tag) { case BUGZILLA: // Note: here we can get the bugzilla version if necessary // String version = attributes.getValue("version"); // urlbase = attributes.getValue("urlbase"); //$NON-NLS-1$ // String maintainer = attributes.getValue("maintainer"); exporter = attributes.getValue("exporter"); //$NON-NLS-1$ break; case BUG: if (attributes != null && (attributes.getValue("error") != null)) { //$NON-NLS-1$ errorMessage = attributes.getValue("error"); //$NON-NLS-1$ bugParseErrorOccurred = true; repositoryTaskData = null; } attachIdToComment = new HashMap<String, TaskCommentMapper>(); commentNum = 0; taskComment = null; longDescs = new ArrayList<TaskComment>(); token = null; break; case LONG_DESC: String is_private = attributes.getValue("isprivate"); //$NON-NLS-1$ taskComment = new TaskComment(is_private); break; case WHO: if (taskComment != null) { if (attributes != null && attributes.getLength() > 0) { String name = attributes.getValue(ATTRIBUTE_NAME); if (name != null && name.length() > 0) { taskComment.authorName = name; } } } break; case REPORTER: if (attributes != null && attributes.getLength() > 0) { String name = attributes.getValue(ATTRIBUTE_NAME); if (name != null) { BugzillaTaskDataHandler.createAttribute(repositoryTaskData, BugzillaAttribute.REPORTER_NAME) .setValue(name); } } break; case QA_CONTACT: if (attributes != null && attributes.getLength() > 0) { String name = attributes.getValue(ATTRIBUTE_NAME); if (name != null) { BugzillaTaskDataHandler.createAttribute(repositoryTaskData, BugzillaAttribute.QA_CONTACT_NAME) .setValue(name); } } break; case ASSIGNED_TO: if (attributes != null && attributes.getLength() > 0) { String name = attributes.getValue(ATTRIBUTE_NAME); if (name != null) { BugzillaTaskDataHandler.createAttribute(repositoryTaskData, BugzillaAttribute.ASSIGNED_TO_NAME) .setValue(name); } } break; case ATTACHMENT: if (attributes != null) { isDeprecated = "1".equals(attributes.getValue(BugzillaAttribute.IS_OBSOLETE.getKey())); //$NON-NLS-1$ isPatch = "1".equals(attributes.getValue(BugzillaAttribute.IS_PATCH.getKey())); //$NON-NLS-1$ } break; case FLAG: if (attributes != null && attributes.getLength() > 0) { String name = attributes.getValue(ATTRIBUTE_NAME); if (name != null) { BugzillaFlagMapper mapper = new BugzillaFlagMapper(connector); String requestee = attributes.getValue("requestee"); //$NON-NLS-1$ mapper.setRequestee(requestee); String setter = attributes.getValue("setter"); //$NON-NLS-1$ mapper.setSetter(setter); String status = attributes.getValue("status"); //$NON-NLS-1$ mapper.setState(status); mapper.setFlagId(name); String id = attributes.getValue("id"); //$NON-NLS-1$ if (id != null && !id.equals("")) { //$NON-NLS-1$ /* * for version 3.2rc1 and 3.2.rc2 the id was not defined so we ignore * the definition */ try { mapper.setNumber(Integer.valueOf(id)); TaskAttribute attribute; if (attachmentAttribute != null) { attribute = attachmentAttribute.createAttribute(BugzillaAttribute.KIND_FLAG + id); } else { attribute = repositoryTaskData.getRoot().createAttribute( BugzillaAttribute.KIND_FLAG + id); } mapper.applyTo(attribute); } catch (NumberFormatException e) { // ignore } } } } break; } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { //remove whitespaces from the end of the parsed Text while (characters.length() > 0 && Character.isWhitespace(characters.charAt(characters.length() - 1))) { characters.setLength(characters.length() - 1); } String parsedText = characters.toString(); if (localName.startsWith(BugzillaCustomField.CUSTOM_FIELD_PREFIX)) { TaskAttribute endAttribute = repositoryTaskData.getRoot().getAttribute(localName); if (endAttribute == null) { String desc = "???"; //$NON-NLS-1$ BugzillaCustomField customField = null; for (BugzillaCustomField bugzillaCustomField : customFields) { if (localName.equals(bugzillaCustomField.getName())) { customField = bugzillaCustomField; break; } } if (customField != null) { TaskAttribute atr = repositoryTaskData.getRoot().createAttribute(localName); desc = customField.getDescription(); atr.getMetaData().defaults().setLabel(desc).setReadOnly(false); atr.getMetaData().setKind(TaskAttribute.KIND_DEFAULT); atr.getMetaData().setType(TaskAttribute.TYPE_SHORT_TEXT); switch (customField.getFieldType()) { case FreeText: atr.getMetaData().setType(TaskAttribute.TYPE_SHORT_TEXT); break; case DropDown: atr.getMetaData().setType(TaskAttribute.TYPE_SINGLE_SELECT); break; case MultipleSelection: atr.getMetaData().setType(TaskAttribute.TYPE_MULTI_SELECT); break; case LargeText: atr.getMetaData().setType(TaskAttribute.TYPE_LONG_TEXT); break; case DateTime: atr.getMetaData().setType(TaskAttribute.TYPE_DATETIME); break; default: List<String> options = customField.getOptions(); if (options.size() > 0) { atr.getMetaData().setType(TaskAttribute.TYPE_SINGLE_SELECT); } else { atr.getMetaData().setType(TaskAttribute.TYPE_SHORT_TEXT); } } atr.getMetaData().setReadOnly(false); atr.setValue(parsedText); } } else { endAttribute.addValue(parsedText); } } BugzillaAttribute tag = BugzillaAttribute.UNKNOWN; try { tag = BugzillaAttribute.valueOf(localName.trim().toUpperCase(Locale.ENGLISH)); } catch (RuntimeException e) { if (e instanceof IllegalArgumentException) { // ignore unrecognized tags return; } throw e; } switch (tag) { case BUG_ID: { repositoryTaskData = taskDataMap.get(parsedText.trim()); if (repositoryTaskData == null) { errorMessage = parsedText + Messages.SaxMultiBugReportContentHandler_id_not_found; bugParseErrorOccurred = true; break; } TaskAttribute attr = repositoryTaskData.getRoot().getMappedAttribute(tag.getKey()); if (attr == null) { attr = BugzillaTaskDataHandler.createAttribute(repositoryTaskData, tag); } attr.setValue(parsedText); if (exporter != null) { createAttrribute(exporter, BugzillaAttribute.EXPORTER_NAME); } else { createAttrribute("", BugzillaAttribute.EXPORTER_NAME); //$NON-NLS-1$ } break; } // Comment attributes case WHO: if (taskComment != null) { taskComment.author = parsedText; } break; case BUG_WHEN: if (taskComment != null) { taskComment.createdTimeStamp = parsedText; } break; case WORK_TIME: if (taskComment != null) { taskComment.timeWorked = parsedText; } break; case THETEXT: if (taskComment != null) { taskComment.commentText = parsedText; } break; case LONG_DESC: if (taskComment != null) { longDescs.add(taskComment); } break; // Attachment attributes case ATTACHID: attachmentAttribute = repositoryTaskData.getRoot().createAttribute( TaskAttribute.PREFIX_ATTACHMENT + parsedText); attachment = BugzillaAttachmentMapper.createFrom(attachmentAttribute); attachment.setLength(new Long(-1)); attachment.setAttachmentId(parsedText); attachment.setPatch(isPatch); attachment.setDeprecated(isDeprecated); attachment.setToken(null); break; case DATE: if (attachment != null) { try { attachment.setCreationDate(simpleFormatter.parse(parsedText)); break; } catch (ParseException e) { } catch (NumberFormatException e) { } } break; case DESC: if (attachment != null) { attachment.setDescription(parsedText); } break; case FILENAME: if (attachment != null) { attachment.setFileName(parsedText); } break; case CTYPE: case TYPE: if (attachment != null) { attachment.setContentType(parsedText); } break; case DELTA_TS: if (attachment != null) { try { if (parsedText != null) { try { attachment.setDeltaDate(simpleFormatter_deltaTS.parse(parsedText)); } catch (ParseException e) { } } } catch (NumberFormatException e) { // ignore } } else { createAttrribute(parsedText, tag); } break; case SIZE: if (attachment != null) { try { if (parsedText != null) { attachment.setLength(Long.parseLong(parsedText)); } } catch (NumberFormatException e) { // ignore } } break; case ATTACHMENT: if (attachment != null) { attachment.applyTo(attachmentAttribute); } isPatch = false; isDeprecated = false; attachment = null; attachmentAttribute = null; break; case DATA: // ignored break; case BUGZILLA: // ignored break; case BUG: // Reached end of bug. if (bugParseErrorOccurred) { bugParseErrorOccurred = false; break; } addDescriptionAndComments(); // Need to set LONGDESCLENGTH to number of comments + 1 for description TaskAttribute numCommentsAttribute = repositoryTaskData.getRoot().getMappedAttribute( BugzillaAttribute.LONGDESCLENGTH.getKey()); if (numCommentsAttribute == null) { numCommentsAttribute = BugzillaTaskDataHandler.createAttribute(repositoryTaskData, BugzillaAttribute.LONGDESCLENGTH); } numCommentsAttribute.setValue("" + commentNum); //$NON-NLS-1$ updateAttachmentMetaData(); TaskAttribute attrCreation = repositoryTaskData.getRoot().getAttribute( BugzillaAttribute.CREATION_TS.getKey()); updateCustomFields(repositoryTaskData); if (token != null) { TaskAttribute tokenAttribute = BugzillaTaskDataHandler.createAttribute(repositoryTaskData, BugzillaAttribute.TOKEN); tokenAttribute.setValue(token); } // Work around (bug#285796) If planning enabled (ESTIMATE_TIME attr present) create DEADLINE attr since it should be present. TaskAttribute estimatedTimeAttr = repositoryTaskData.getRoot().getMappedAttribute( BugzillaAttribute.ESTIMATED_TIME.getKey()); TaskAttribute deadlineAttr = repositoryTaskData.getRoot().getMappedAttribute( BugzillaAttribute.DEADLINE.getKey()); if (estimatedTimeAttr != null && deadlineAttr == null) { BugzillaTaskDataHandler.createAttribute(repositoryTaskData, BugzillaAttribute.DEADLINE); } // Save/Add to collector. Guard against empty data sets if (attrCreation != null && !attrCreation.equals("")) { //$NON-NLS-1$ collector.accept(repositoryTaskData); } repositoryTaskData = null; break; case BLOCKED: // handled similarly to DEPENDSON case DEPENDSON: TaskAttribute blockOrDepends = repositoryTaskData.getRoot().getMappedAttribute(tag.getKey()); if (blockOrDepends == null) { BugzillaTaskDataHandler.createAttribute(repositoryTaskData, tag).setValue(parsedText); } else { if (blockOrDepends.getValue().equals("")) { //$NON-NLS-1$ blockOrDepends.setValue(parsedText); } else { blockOrDepends.setValue(blockOrDepends.getValue() + ", " + parsedText); //$NON-NLS-1$ } } break; case DUP_ID: TaskAttribute duplicateOf = repositoryTaskData.getRoot().getMappedAttribute(tag.getKey()); if (duplicateOf == null) { BugzillaTaskDataHandler.createAttribute(repositoryTaskData, tag).setValue(parsedText); } else { if (duplicateOf.getValue().equals("")) { //$NON-NLS-1$ duplicateOf.setValue(parsedText); } } break; case UNKNOWN: //ignore break; case FLAG: //ignore break; case TOKEN: if (attachment != null) { attachment.setToken(parsedText); } else { token = parsedText; } break; case ATTACHER: if (attachment != null) { IRepositoryPerson author = repositoryTaskData.getAttributeMapper() .getTaskRepository() .createPerson(parsedText); attachment.setAuthor(author); } break; case TARGET_MILESTONE: BugzillaUtil.createAttributeWithKindDefaultIfUsed(parsedText, tag, repositoryTaskData, IBugzillaConstants.BUGZILLA_PARAM_USETARGETMILESTONE, true); break; case QA_CONTACT: BugzillaUtil.createAttributeWithKindDefaultIfUsed(parsedText, tag, repositoryTaskData, IBugzillaConstants.BUGZILLA_PARAM_USEQACONTACT, true); break; case STATUS_WHITEBOARD: BugzillaUtil.createAttributeWithKindDefaultIfUsed(parsedText, tag, repositoryTaskData, IBugzillaConstants.BUGZILLA_PARAM_USESTATUSWHITEBOARD, true); break; case CLASSIFICATION: BugzillaUtil.createAttributeWithKindDefaultIfUsed(parsedText, tag, repositoryTaskData, IBugzillaConstants.BUGZILLA_PARAM_USECLASSIFICATION, false); break; case ALIAS: BugzillaUtil.createAttributeWithKindDefaultIfUsed(parsedText, tag, repositoryTaskData, IBugzillaConstants.BUGZILLA_PARAM_USEBUGALIASES, false); break; case SEE_ALSO: BugzillaUtil.createAttributeWithKindDefaultIfUsed(parsedText, tag, repositoryTaskData, IBugzillaConstants.BUGZILLA_PARAM_USE_SEE_ALSO, false); break; case COMMENTID: if (taskComment != null) { taskComment.id = Integer.parseInt(parsedText); } break; default: createAttrribute(parsedText, tag); break; } } private TaskAttribute createAttrribute(String parsedText, BugzillaAttribute tag) { TaskAttribute attribute = repositoryTaskData.getRoot().getMappedAttribute(tag.getKey()); if (attribute == null) { attribute = BugzillaTaskDataHandler.createAttribute(repositoryTaskData, tag); attribute.setValue(parsedText); } else { attribute.addValue(parsedText); } return attribute; } private void updateCustomFields(TaskData taskData) { RepositoryConfiguration config = connector.getRepositoryConfiguration(repositoryTaskData.getRepositoryUrl()); if (config != null) { for (BugzillaCustomField bugzillaCustomField : config.getCustomFields()) { TaskAttribute atr = taskData.getRoot().getAttribute(bugzillaCustomField.getName()); if (atr == null) { atr = taskData.getRoot().createAttribute(bugzillaCustomField.getName()); } if (atr != null) { atr.getMetaData().defaults().setLabel(bugzillaCustomField.getDescription()); atr.getMetaData().setKind(TaskAttribute.KIND_DEFAULT); switch (bugzillaCustomField.getFieldType()) { case FreeText: atr.getMetaData().setType(TaskAttribute.TYPE_SHORT_TEXT); break; case DropDown: atr.getMetaData().setType(TaskAttribute.TYPE_SINGLE_SELECT); break; case MultipleSelection: atr.getMetaData().setType(TaskAttribute.TYPE_MULTI_SELECT); break; case LargeText: atr.getMetaData().setType(TaskAttribute.TYPE_LONG_TEXT); break; case DateTime: atr.getMetaData().setType(TaskAttribute.TYPE_DATETIME); break; default: List<String> options = bugzillaCustomField.getOptions(); if (options.size() > 0) { atr.getMetaData().setType(TaskAttribute.TYPE_SINGLE_SELECT); } else { atr.getMetaData().setType(TaskAttribute.TYPE_SHORT_TEXT); } } atr.getMetaData().setReadOnly(false); } } } } private void updateAttachmentMetaData() { List<TaskAttribute> taskAttachments = repositoryTaskData.getAttributeMapper().getAttributesByType( repositoryTaskData, TaskAttribute.TYPE_ATTACHMENT); for (TaskAttribute attachment : taskAttachments) { BugzillaAttachmentMapper attachmentMapper = BugzillaAttachmentMapper.createFrom(attachment); if (attachmentMapper.getAuthor() == null || attachmentMapper.getCreationDate() == null) { TaskCommentMapper taskComment = attachIdToComment.get(attachmentMapper.getAttachmentId()); if (taskComment != null) { attachmentMapper.setAuthor(taskComment.getAuthor()); attachmentMapper.setCreationDate(taskComment.getCreationDate()); } } attachmentMapper.setUrl(repositoryTaskData.getRepositoryUrl() + IBugzillaConstants.URL_GET_ATTACHMENT_SUFFIX + attachmentMapper.getAttachmentId()); attachmentMapper.applyTo(attachment); } } private void addDescriptionAndComments() { int longDescsSize = longDescs.size() - 1; commentNum = 1; if (longDescsSize == 0) { addDescription(longDescs.get(0)); } else if (longDescsSize == 1) { if (longDescs.get(0).createdTimeStamp.compareTo(longDescs.get(1).createdTimeStamp) <= 0) { // if created_0 is equal to created_1 we assume that longDescs at index 0 is the description. addDescription(longDescs.get(0)); addComment(longDescs.get(1)); } else { addDescription(longDescs.get(1)); addComment(longDescs.get(0)); } } else if (longDescsSize > 1) { String created_0 = longDescs.get(0).createdTimeStamp; String created_1 = longDescs.get(1).createdTimeStamp; String created_n = longDescs.get(longDescsSize).createdTimeStamp; if (created_0.compareTo(created_1) <= 0 && created_0.compareTo(created_n) < 0) { // if created_0 is equal to created_1 we assume that longDescs at index 0 is the description. addDescription(longDescs.get(0)); if (created_1.compareTo(created_n) < 0) { for (int i = 1; i <= longDescsSize; i++) { addComment(longDescs.get(i)); } } else { for (int i = longDescsSize; i > 0; i--) { addComment(longDescs.get(i)); } } } else { addDescription(longDescs.get(longDescsSize)); if (created_0.compareTo(created_1) < 0) { for (int i = 0; i < longDescsSize; i++) { addComment(longDescs.get(i)); } } else { for (int i = longDescsSize - 1; i >= 0; i--) { addComment(longDescs.get(i)); } } } } } private void addDescription(TaskComment comment) { TaskAttribute attrDescription = BugzillaTaskDataHandler.createAttribute(repositoryTaskData, BugzillaAttribute.LONG_DESC); TaskAttribute idAttribute = attrDescription.createAttribute(IBugzillaConstants.BUGZILLA_DESCRIPTION_ID); TaskAttribute isprivateAttribute = attrDescription.createAttribute(IBugzillaConstants.BUGZILLA_DESCRIPTION_IS_PRIVATE); attrDescription.setValue(comment.commentText); idAttribute.setValue(Integer.toString(comment.id)); if (comment.isPrivate != null) { isprivateAttribute.setValue(comment.isPrivate); } if (!useIsPrivate) { if ("1".equals(comment.isPrivate)) { //$NON-NLS-1$ TaskRepository taskRepository = mapper.getTaskRepository(); taskRepository.setProperty(IBugzillaConstants.BUGZILLA_INSIDER_GROUP, "true"); //$NON-NLS-1$ useIsPrivate = true; } } } private void addComment(TaskComment comment) { TaskAttribute attribute = repositoryTaskData.getRoot().createAttribute( TaskAttribute.PREFIX_COMMENT + commentNum); TaskCommentMapper taskComment = TaskCommentMapper.createFrom(attribute); taskComment.setCommentId(comment.id + ""); //$NON-NLS-1$ taskComment.setNumber(commentNum); IRepositoryPerson author = repositoryTaskData.getAttributeMapper() .getTaskRepository() .createPerson(comment.author); author.setName(comment.authorName); taskComment.setAuthor(author); if (useIsPrivate) { taskComment.setIsPrivate("1".equals(comment.isPrivate)); //$NON-NLS-1$ } else { if ("1".equals(comment.isPrivate)) { //$NON-NLS-1$ TaskRepository taskRepository = mapper.getTaskRepository(); taskRepository.setProperty(IBugzillaConstants.BUGZILLA_INSIDER_GROUP, "true"); //$NON-NLS-1$ useIsPrivate = true; taskComment.setIsPrivate("1".equals(comment.isPrivate)); //$NON-NLS-1$ } else { taskComment.setIsPrivate(null); } } TaskAttribute attrTimestamp = attribute.createAttribute(BugzillaAttribute.BUG_WHEN.getKey()); attrTimestamp.setValue(comment.createdTimeStamp); taskComment.setCreationDate(repositoryTaskData.getAttributeMapper().getDateValue(attrTimestamp)); if (comment.commentText != null) { String commentText = comment.commentText.trim(); taskComment.setText(commentText); } taskComment.applyTo(attribute); commentNum++; if (comment.timeWorked != null) { TaskAttribute workTime = BugzillaTaskDataHandler.createAttribute(attribute, BugzillaAttribute.WORK_TIME); workTime.setValue(comment.timeWorked); } parseAttachment(taskComment); } /** determines attachment id from comment */ private void parseAttachment(TaskCommentMapper comment) { String attachmentID = ""; //$NON-NLS-1$ String commentText = comment.getText(); int firstDelimiter = commentText.indexOf("\n"); //$NON-NLS-1$ if (firstDelimiter < 0) { firstDelimiter = commentText.length(); } int startIndex = commentText.indexOf(ID_STRING_BEGIN); if (startIndex > 0 && startIndex < firstDelimiter) { int endIndex = commentText.indexOf(ID_STRING_END, startIndex); if (endIndex > 0 && endIndex < firstDelimiter) { startIndex += ID_STRING_BEGIN.length(); int p = startIndex; while (p < endIndex) { char c = commentText.charAt(p); if (c < '0' || c > '9') { break; } p++; } if (p == endIndex) { attachmentID = commentText.substring(startIndex, endIndex); if (!attachmentID.equals("")) { //$NON-NLS-1$ attachIdToComment.put(attachmentID, comment); } } } } } private static class TaskComment { @SuppressWarnings("unused") public int number; public int id; public String author; public String authorName; public String createdTimeStamp; public String commentText; public String timeWorked; public String isPrivate; public TaskComment(String isprivate) { this.isPrivate = isprivate; } } }