diff options
5 files changed, 2656 insertions, 2656 deletions
diff --git a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttribute.java b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttribute.java index 2f0bc36e5..50f8382c2 100644 --- a/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttribute.java +++ b/org.eclipse.mylyn.tasks.core/src/org/eclipse/mylyn/tasks/core/data/TaskAttribute.java @@ -1,612 +1,612 @@ -/*******************************************************************************
- * Copyright (c) 2004, 2012 Tasktop Technologies and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Tasktop Technologies - initial API and implementation
- *******************************************************************************/
-
-package org.eclipse.mylyn.tasks.core.data;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.eclipse.core.runtime.Assert;
-
-/**
- * Encapsulates attributes for task data.
- *
- * @author Rob Elves
- * @author Steffen Pingel
- * @since 3.0
- */
-public final class TaskAttribute {
-
- /**
- * Boolean attribute. If true, repository user needs to be added to the cc list.
- */
- public static final String ADD_SELF_CC = "task.common.addselfcc"; //$NON-NLS-1$
-
- public static final String ATTACHMENT_AUTHOR = "task.common.attachment.author"; //$NON-NLS-1$
-
- public static final String ATTACHMENT_CONTENT_TYPE = "task.common.attachment.ctype"; //$NON-NLS-1$
-
- public static final String ATTACHMENT_DATE = "task.common.attachment.date"; //$NON-NLS-1$
-
- public static final String ATTACHMENT_DESCRIPTION = "task.common.attachment.description"; //$NON-NLS-1$
-
- public static final String ATTACHMENT_FILENAME = "filename"; //$NON-NLS-1$
-
- public static final String ATTACHMENT_ID = "task.common.attachment.id"; //$NON-NLS-1$
-
- public static final String ATTACHMENT_IS_DEPRECATED = "task.common.attachment.deprecated"; //$NON-NLS-1$
-
- public static final String ATTACHMENT_IS_PATCH = "task.common.attachment.patch"; //$NON-NLS-1$
-
- /**
- * @since 3.4
- */
- public static final String ATTACHMENT_REPLACE_EXISTING = "task.common.attachment.replaceExisting"; //$NON-NLS-1$
-
- public static final String ATTACHMENT_SIZE = "task.common.attachment.size"; //$NON-NLS-1$
-
- public static final String ATTACHMENT_URL = "task.common.attachment.url"; //$NON-NLS-1$
-
- public static final String COMMENT_ATTACHMENT_ID = "task.common.comment.attachment.id"; //$NON-NLS-1$
-
- public static final String COMMENT_AUTHOR = "task.common.comment.author"; //$NON-NLS-1$
-
- @Deprecated
- public static final String COMMENT_AUTHOR_NAME = "task.common.comment.author.name"; //$NON-NLS-1$
-
- public static final String COMMENT_DATE = "task.common.comment.date"; //$NON-NLS-1$
-
- public static final String COMMENT_HAS_ATTACHMENT = "task.common.comment.attachment"; //$NON-NLS-1$
-
- public static final String COMMENT_NEW = "task.common.comment.new"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String COMMENT_NUMBER = "task.common.comment.number"; //$NON-NLS-1$
-
- public static final String COMMENT_TEXT = "task.common.comment.text"; //$NON-NLS-1$
-
- public static final String COMMENT_URL = "task.common.comment.url"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String COMPONENT = "task.common.component"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String DATE_COMPLETION = "task.common.date.completed"; //$NON-NLS-1$
-
- public static final String DATE_CREATION = "task.common.date.created"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String DATE_DUE = "task.common.date.due"; //$NON-NLS-1$
-
- public static final String DATE_MODIFICATION = "task.common.date.modified"; //$NON-NLS-1$
-
- public static final String DESCRIPTION = "task.common.description"; //$NON-NLS-1$
-
- public static final String KEYWORDS = "task.common.keywords"; //$NON-NLS-1$
-
- public static final String KIND_DEFAULT = "task.common.kind.default"; //$NON-NLS-1$
-
- public static final String KIND_OPERATION = "task.common.kind.operation"; //$NON-NLS-1$
-
- public static final String KIND_PEOPLE = "task.common.kind.people"; //$NON-NLS-1$
-
- //public static final String META_SHOW_IN_ATTRIBUTES_SECTION = "task.meta.showInTaskEditorAttributesSection";
-
- public static final String META_ASSOCIATED_ATTRIBUTE_ID = "task.meta.associated.attribute"; //$NON-NLS-1$
-
- public static final String META_ATTRIBUTE_KIND = "task.meta.attributeKind"; //$NON-NLS-1$
-
- public static final String META_ATTRIBUTE_TYPE = "task.meta.type"; //$NON-NLS-1$
-
- public static final String META_DEFAULT_OPTION = "task.meta.defaultOption"; //$NON-NLS-1$
-
-// public static final String META_DETAIL_LEVEL = "task.meta.detailLevel";
-
- public static final String META_LABEL = "task.meta.label"; //$NON-NLS-1$
-
- public static final String META_READ_ONLY = "task.meta.readOnly"; //$NON-NLS-1$
-
- /**
- * @since 3.6
- */
- public static final String COMMENT_ISPRIVATE = "task.common.comment.isprivate"; //$NON-NLS-1$
-
- /**
- * Key for the meta datum that determines if an attribute is disabled. This is used to indicate that an attribute
- * should not be modified, e.g. due to work-flow state but it may still be generally writeable.
- *
- * @since 3.5
- * @see TaskAttributeMetaData#isDisabled()
- */
- public static final String META_DISABLED = "task.meta.disabled"; //$NON-NLS-1$
-
- /**
- * Key for the meta datum that provides a description of an attribute, e.g. for display in a tooltip.
- *
- * @since 3.5
- * @see TaskAttributeMetaData
- */
- public static final String META_DESCRIPTION = "task.meta.description"; //$NON-NLS-1$
-
- /**
- * Task attribute meta-data key that should be set to "true" to have attribute value indexed as part of the task
- * content. Provides a way for connectors to specify non-standard attributes as plain-text indexable. By default,
- * {@link #SUMMARY summary} and {@link #DESCRIPTION description} are indexed. Note that setting this meta-data is
- * advisory only and will not guarantee that content is indexed.
- *
- * @since 3.7
- */
- public static final String META_INDEXED_AS_CONTENT = "task.meta.index.content"; //$NON-NLS-1$
-
- public static final String NEW_ATTACHMENT = "task.common.new.attachment"; //$NON-NLS-1$
-
- // XXX merge with USER_CC
- //public static final String NEW_CC = "task.common.newcc";
-
- public static final String OPERATION = "task.common.operation"; //$NON-NLS-1$
-
- public static final String PERSON_NAME = "task.common.person.name"; //$NON-NLS-1$
-
- public static final String PREFIX_ATTACHMENT = "task.common.attachment-"; //$NON-NLS-1$
-
- public static final String PREFIX_COMMENT = "task.common.comment-"; //$NON-NLS-1$
-
- // XXX merge with USER_CC
- //public static final String REMOVE_CC = "task.common.removecc";
-
- public static final String PREFIX_OPERATION = "task.common.operation-"; //$NON-NLS-1$
-
- public static final String PRIORITY = "task.common.priority"; //$NON-NLS-1$
-
- public static final String PRODUCT = "task.common.product"; //$NON-NLS-1$
-
- public static final String RESOLUTION = "task.common.resolution"; //$NON-NLS-1$
-
- public static final String STATUS = "task.common.status"; //$NON-NLS-1$
-
- public static final String SUMMARY = "task.common.summary"; //$NON-NLS-1$
-
- public static final String TASK_KEY = "task.common.key"; //$NON-NLS-1$
-
- public static final String TASK_KIND = "task.common.kind"; //$NON-NLS-1$
-
- /**
- * @since 3.3
- */
- public static final String RANK = "task.common.rank"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TASK_URL = "task.common.url"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TYPE_ATTACHMENT = "attachment"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TYPE_BOOLEAN = "boolean"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TYPE_COMMENT = "comment"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TYPE_CONTAINER = "container"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TYPE_DATE = "date"; //$NON-NLS-1$
-
- /**
- * @since 3.1
- */
- public static final String TYPE_DATETIME = "dateTime"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TYPE_INTEGER = "integer"; //$NON-NLS-1$
-
- /**
- * @since 3.1
- */
- public static final String TYPE_LONG = "long"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TYPE_LONG_RICH_TEXT = "longRichText"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TYPE_LONG_TEXT = "longText"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TYPE_MULTI_SELECT = "multiSelect"; //$NON-NLS-1$
-
- public static final String TYPE_OPERATION = "operation"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TYPE_PERSON = "person"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TYPE_SHORT_RICH_TEXT = "shortRichText"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TYPE_SHORT_TEXT = "shortText"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TYPE_SINGLE_SELECT = "singleSelect"; //$NON-NLS-1$
-
- /**
- * @since 3.0
- */
- public static final String TYPE_TASK_DEPENDENCY = "taskDepenedency"; //$NON-NLS-1$
-
- public static final String TYPE_URL = "url"; //$NON-NLS-1$
-
- /**
- * @since 3.5
- */
- public static final String TYPE_DOUBLE = "double"; //$NON-NLS-1$
-
- public static final String USER_ASSIGNED = "task.common.user.assigned"; //$NON-NLS-1$
-
- @Deprecated
- public static final String USER_ASSIGNED_NAME = "task.common.user.assigned.name"; //$NON-NLS-1$
-
- public static final String USER_CC = "task.common.user.cc"; //$NON-NLS-1$
-
- public static final String USER_REPORTER = "task.common.user.reporter"; //$NON-NLS-1$
-
- @Deprecated
- public static final String USER_REPORTER_NAME = "task.common.user.reporter.name"; //$NON-NLS-1$
-
- /**
- * @since 3.2
- */
- public static final String SEVERITY = "task.common.severity"; //$NON-NLS-1$
-
- /**
- * @since 3.2
- */
- public static final String VERSION = "task.common.version"; //$NON-NLS-1$
-
- private Map<String, TaskAttribute> attributeById;
-
- private final String attributeId;
-
- private Map<String, String> metaData;
-
- private Map<String, String> optionByKey;
-
- private final TaskAttribute parentAttribute;
-
- private final TaskData taskData;
-
- /**
- * Attribute's values (selected or added)
- */
- private final List<String> values;
-
- public TaskAttribute(TaskAttribute parentAttribute, String attributeId) {
- Assert.isNotNull(parentAttribute);
- Assert.isNotNull(attributeId);
- this.parentAttribute = parentAttribute;
- this.attributeId = attributeId.intern();
- this.taskData = parentAttribute.getTaskData();
- this.values = new ArrayList<String>(1);
- parentAttribute.add(this);
- }
-
- /**
- * Constructor for the root node.
- */
- TaskAttribute(TaskData taskData) {
- Assert.isNotNull(taskData);
- this.parentAttribute = null;
- this.taskData = taskData;
- this.attributeId = "root"; //$NON-NLS-1$
- this.values = new ArrayList<String>(1);
- }
-
- private void add(TaskAttribute attribute) {
- if (attributeById == null) {
- attributeById = new LinkedHashMap<String, TaskAttribute>();
- }
- attributeById.put(attribute.getId(), attribute);
- }
-
- public void addValue(String value) {
- Assert.isNotNull(value);
- values.add(value);
- }
-
- public void clearAttributes() {
- attributeById = null;
- }
-
- void clearMetaDataMap() {
- metaData = null;
- }
-
- public void clearOptions() {
- optionByKey = null;
- }
-
- public void clearValues() {
- values.clear();
- }
-
- public TaskAttribute createAttribute(String attributeId) {
- return new TaskAttribute(this, attributeId);
- }
-
- public void deepAddCopy(TaskAttribute source) {
- TaskAttribute target = createAttribute(source.getId());
- target.values.addAll(source.values);
- if (source.metaData != null) {
- target.metaData = new LinkedHashMap<String, String>(source.metaData);
- }
- if (source.optionByKey != null) {
- target.optionByKey = new LinkedHashMap<String, String>(source.optionByKey);
- }
- if (source.attributeById != null) {
- for (TaskAttribute child : source.attributeById.values()) {
- target.deepAddCopy(child);
- }
- }
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
- return false;
- }
- final TaskAttribute other = (TaskAttribute) obj;
- if (attributeId == null) {
- if (other.attributeId != null) {
- return false;
- }
- } else if (!attributeId.equals(other.attributeId)) {
- return false;
- }
- return true;
- }
-
- public TaskAttribute getAttribute(String attributeId) {
- Assert.isNotNull(attributeId);
- return (attributeById != null) ? attributeById.get(attributeId) : null;
- }
-
- public Map<String, TaskAttribute> getAttributes() {
- if (attributeById != null) {
- return Collections.unmodifiableMap(attributeById);
- } else {
- return Collections.emptyMap();
- }
- }
-
- public String getId() {
- return attributeId;
- }
-
- public TaskAttribute getMappedAttribute(String attributeId) {
- Assert.isNotNull(attributeId);
- return (attributeById != null) ? attributeById.get(getTaskData().getAttributeMapper().mapToRepositoryKey(this,
- attributeId)) : null;
- }
-
- public TaskAttribute getMappedAttribute(String[] path) {
- TaskAttribute attribute = this;
- for (String id : path) {
- attribute = attribute.getMappedAttribute(id);
- if (attribute == null) {
- break;
- }
- }
- return attribute;
- }
-
- String getMetaDatum(String key) {
- return (metaData != null) ? metaData.get(key) : null;
- }
-
- Map<String, String> getMetaDataMap() {
- if (metaData != null) {
- return Collections.unmodifiableMap(metaData);
- } else {
- return Collections.emptyMap();
- }
- }
-
- public String getOption(String key) {
- return (optionByKey != null) ? optionByKey.get(key) : null;
- }
-
- public Map<String, String> getOptions() {
- if (optionByKey != null) {
- return Collections.unmodifiableMap(optionByKey);
- } else {
- return Collections.emptyMap();
- }
- }
-
- public TaskAttribute getParentAttribute() {
- return parentAttribute;
- }
-
- public String[] getPath() {
- List<String> path = new ArrayList<String>();
- TaskAttribute attribute = this;
- while (attribute.getParentAttribute() != null) {
- path.add(attribute.getId());
- attribute = attribute.getParentAttribute();
- }
- Collections.reverse(path);
- return path.toArray(new String[0]);
- }
-
- public TaskAttributeMetaData getMetaData() {
- return new TaskAttributeMetaData(this);
- }
-
- public TaskData getTaskData() {
- return taskData;
- }
-
- /**
- * @return empty String if not available
- */
- public String getValue() {
- if (values.size() > 0) {
- return values.get(0);
- } else {
- return ""; //$NON-NLS-1$
- }
- }
-
- public List<String> getValues() {
- return Collections.unmodifiableList(values);
- }
-
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + ((attributeId == null) ? 0 : attributeId.hashCode());
- return result;
- }
-
- void putMetaDatum(String key, String value) {
- Assert.isNotNull(key);
- Assert.isNotNull(value);
- if (metaData == null) {
- metaData = new LinkedHashMap<String, String>();
- }
- metaData.put(key, value);
- }
-
- /**
- * Adds an attribute option value
- *
- * @param readableValue
- * The value displayed on the screen
- * @param parameterValue
- * The option value used when sending the form to the server
- */
- public void putOption(String key, String value) {
- Assert.isNotNull(key);
- Assert.isNotNull(value);
- if (optionByKey == null) {
- optionByKey = new LinkedHashMap<String, String>();
- }
- optionByKey.put(key, value);
- }
-
- public void removeAttribute(String attributeId) {
- if (attributeById != null) {
- attributeById.remove(attributeId);
- }
- }
-
- void removeMetaDatum(String metaDataId) {
- if (metaData != null) {
- metaData.remove(metaDataId);
- }
- }
-
- public void removeValue(String value) {
- values.remove(value);
- }
-
- public void setValue(String value) {
- Assert.isNotNull(value);
- if (values.size() > 0) {
- values.clear();
- }
- values.add(value);
- }
-
- public void setValues(List<String> values) {
- this.values.clear();
- this.values.addAll(values);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- toString(sb, ""); //$NON-NLS-1$
- return sb.toString();
- }
-
- private void toString(StringBuilder sb, String prefix) {
- sb.append(prefix);
- sb.append("TaskAttribute[id="); //$NON-NLS-1$
- sb.append(attributeId);
- sb.append(",values="); //$NON-NLS-1$
- sb.append(values);
- sb.append(",options="); //$NON-NLS-1$
- sb.append(optionByKey);
- sb.append(",metaData="); //$NON-NLS-1$
- sb.append(metaData);
- sb.append("]"); //$NON-NLS-1$
- if (attributeById != null) {
- for (TaskAttribute child : attributeById.values()) {
- sb.append("\n"); //$NON-NLS-1$
- child.toString(sb, prefix + " "); //$NON-NLS-1$
- }
- }
- }
-
- public TaskAttribute createMappedAttribute(String attributeId) {
- Assert.isNotNull(attributeId);
- String mappedAttributeId = getTaskData().getAttributeMapper().mapToRepositoryKey(this, attributeId);
- Assert.isNotNull(mappedAttributeId);
- return new TaskAttribute(this, mappedAttributeId);
- }
-}
+/******************************************************************************* + * Copyright (c) 2004, 2012 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.tasks.core.data; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.Assert; + +/** + * Encapsulates attributes for task data. + * + * @author Rob Elves + * @author Steffen Pingel + * @since 3.0 + */ +public final class TaskAttribute { + + /** + * Boolean attribute. If true, repository user needs to be added to the cc list. + */ + public static final String ADD_SELF_CC = "task.common.addselfcc"; //$NON-NLS-1$ + + public static final String ATTACHMENT_AUTHOR = "task.common.attachment.author"; //$NON-NLS-1$ + + public static final String ATTACHMENT_CONTENT_TYPE = "task.common.attachment.ctype"; //$NON-NLS-1$ + + public static final String ATTACHMENT_DATE = "task.common.attachment.date"; //$NON-NLS-1$ + + public static final String ATTACHMENT_DESCRIPTION = "task.common.attachment.description"; //$NON-NLS-1$ + + public static final String ATTACHMENT_FILENAME = "filename"; //$NON-NLS-1$ + + public static final String ATTACHMENT_ID = "task.common.attachment.id"; //$NON-NLS-1$ + + public static final String ATTACHMENT_IS_DEPRECATED = "task.common.attachment.deprecated"; //$NON-NLS-1$ + + public static final String ATTACHMENT_IS_PATCH = "task.common.attachment.patch"; //$NON-NLS-1$ + + /** + * @since 3.4 + */ + public static final String ATTACHMENT_REPLACE_EXISTING = "task.common.attachment.replaceExisting"; //$NON-NLS-1$ + + public static final String ATTACHMENT_SIZE = "task.common.attachment.size"; //$NON-NLS-1$ + + public static final String ATTACHMENT_URL = "task.common.attachment.url"; //$NON-NLS-1$ + + public static final String COMMENT_ATTACHMENT_ID = "task.common.comment.attachment.id"; //$NON-NLS-1$ + + public static final String COMMENT_AUTHOR = "task.common.comment.author"; //$NON-NLS-1$ + + @Deprecated + public static final String COMMENT_AUTHOR_NAME = "task.common.comment.author.name"; //$NON-NLS-1$ + + public static final String COMMENT_DATE = "task.common.comment.date"; //$NON-NLS-1$ + + public static final String COMMENT_HAS_ATTACHMENT = "task.common.comment.attachment"; //$NON-NLS-1$ + + public static final String COMMENT_NEW = "task.common.comment.new"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String COMMENT_NUMBER = "task.common.comment.number"; //$NON-NLS-1$ + + public static final String COMMENT_TEXT = "task.common.comment.text"; //$NON-NLS-1$ + + public static final String COMMENT_URL = "task.common.comment.url"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String COMPONENT = "task.common.component"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String DATE_COMPLETION = "task.common.date.completed"; //$NON-NLS-1$ + + public static final String DATE_CREATION = "task.common.date.created"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String DATE_DUE = "task.common.date.due"; //$NON-NLS-1$ + + public static final String DATE_MODIFICATION = "task.common.date.modified"; //$NON-NLS-1$ + + public static final String DESCRIPTION = "task.common.description"; //$NON-NLS-1$ + + public static final String KEYWORDS = "task.common.keywords"; //$NON-NLS-1$ + + public static final String KIND_DEFAULT = "task.common.kind.default"; //$NON-NLS-1$ + + public static final String KIND_OPERATION = "task.common.kind.operation"; //$NON-NLS-1$ + + public static final String KIND_PEOPLE = "task.common.kind.people"; //$NON-NLS-1$ + + //public static final String META_SHOW_IN_ATTRIBUTES_SECTION = "task.meta.showInTaskEditorAttributesSection"; + + public static final String META_ASSOCIATED_ATTRIBUTE_ID = "task.meta.associated.attribute"; //$NON-NLS-1$ + + public static final String META_ATTRIBUTE_KIND = "task.meta.attributeKind"; //$NON-NLS-1$ + + public static final String META_ATTRIBUTE_TYPE = "task.meta.type"; //$NON-NLS-1$ + + public static final String META_DEFAULT_OPTION = "task.meta.defaultOption"; //$NON-NLS-1$ + +// public static final String META_DETAIL_LEVEL = "task.meta.detailLevel"; + + public static final String META_LABEL = "task.meta.label"; //$NON-NLS-1$ + + public static final String META_READ_ONLY = "task.meta.readOnly"; //$NON-NLS-1$ + + /** + * @since 3.6 + */ + public static final String COMMENT_ISPRIVATE = "task.common.comment.isprivate"; //$NON-NLS-1$ + + /** + * Key for the meta datum that determines if an attribute is disabled. This is used to indicate that an attribute + * should not be modified, e.g. due to work-flow state but it may still be generally writeable. + * + * @since 3.5 + * @see TaskAttributeMetaData#isDisabled() + */ + public static final String META_DISABLED = "task.meta.disabled"; //$NON-NLS-1$ + + /** + * Key for the meta datum that provides a description of an attribute, e.g. for display in a tooltip. + * + * @since 3.5 + * @see TaskAttributeMetaData + */ + public static final String META_DESCRIPTION = "task.meta.description"; //$NON-NLS-1$ + + /** + * Task attribute meta-data key that should be set to "true" to have attribute value indexed as part of the task + * content. Provides a way for connectors to specify non-standard attributes as plain-text indexable. By default, + * {@link #SUMMARY summary} and {@link #DESCRIPTION description} are indexed. Note that setting this meta-data is + * advisory only and will not guarantee that content is indexed. + * + * @since 3.7 + */ + public static final String META_INDEXED_AS_CONTENT = "task.meta.index.content"; //$NON-NLS-1$ + + public static final String NEW_ATTACHMENT = "task.common.new.attachment"; //$NON-NLS-1$ + + // XXX merge with USER_CC + //public static final String NEW_CC = "task.common.newcc"; + + public static final String OPERATION = "task.common.operation"; //$NON-NLS-1$ + + public static final String PERSON_NAME = "task.common.person.name"; //$NON-NLS-1$ + + public static final String PREFIX_ATTACHMENT = "task.common.attachment-"; //$NON-NLS-1$ + + public static final String PREFIX_COMMENT = "task.common.comment-"; //$NON-NLS-1$ + + // XXX merge with USER_CC + //public static final String REMOVE_CC = "task.common.removecc"; + + public static final String PREFIX_OPERATION = "task.common.operation-"; //$NON-NLS-1$ + + public static final String PRIORITY = "task.common.priority"; //$NON-NLS-1$ + + public static final String PRODUCT = "task.common.product"; //$NON-NLS-1$ + + public static final String RESOLUTION = "task.common.resolution"; //$NON-NLS-1$ + + public static final String STATUS = "task.common.status"; //$NON-NLS-1$ + + public static final String SUMMARY = "task.common.summary"; //$NON-NLS-1$ + + public static final String TASK_KEY = "task.common.key"; //$NON-NLS-1$ + + public static final String TASK_KIND = "task.common.kind"; //$NON-NLS-1$ + + /** + * @since 3.3 + */ + public static final String RANK = "task.common.rank"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TASK_URL = "task.common.url"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_ATTACHMENT = "attachment"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_BOOLEAN = "boolean"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_COMMENT = "comment"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_CONTAINER = "container"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_DATE = "date"; //$NON-NLS-1$ + + /** + * @since 3.1 + */ + public static final String TYPE_DATETIME = "dateTime"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_INTEGER = "integer"; //$NON-NLS-1$ + + /** + * @since 3.1 + */ + public static final String TYPE_LONG = "long"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_LONG_RICH_TEXT = "longRichText"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_LONG_TEXT = "longText"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_MULTI_SELECT = "multiSelect"; //$NON-NLS-1$ + + public static final String TYPE_OPERATION = "operation"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_PERSON = "person"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_SHORT_RICH_TEXT = "shortRichText"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_SHORT_TEXT = "shortText"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_SINGLE_SELECT = "singleSelect"; //$NON-NLS-1$ + + /** + * @since 3.0 + */ + public static final String TYPE_TASK_DEPENDENCY = "taskDepenedency"; //$NON-NLS-1$ + + public static final String TYPE_URL = "url"; //$NON-NLS-1$ + + /** + * @since 3.5 + */ + public static final String TYPE_DOUBLE = "double"; //$NON-NLS-1$ + + public static final String USER_ASSIGNED = "task.common.user.assigned"; //$NON-NLS-1$ + + @Deprecated + public static final String USER_ASSIGNED_NAME = "task.common.user.assigned.name"; //$NON-NLS-1$ + + public static final String USER_CC = "task.common.user.cc"; //$NON-NLS-1$ + + public static final String USER_REPORTER = "task.common.user.reporter"; //$NON-NLS-1$ + + @Deprecated + public static final String USER_REPORTER_NAME = "task.common.user.reporter.name"; //$NON-NLS-1$ + + /** + * @since 3.2 + */ + public static final String SEVERITY = "task.common.severity"; //$NON-NLS-1$ + + /** + * @since 3.2 + */ + public static final String VERSION = "task.common.version"; //$NON-NLS-1$ + + private Map<String, TaskAttribute> attributeById; + + private final String attributeId; + + private Map<String, String> metaData; + + private Map<String, String> optionByKey; + + private final TaskAttribute parentAttribute; + + private final TaskData taskData; + + /** + * Attribute's values (selected or added) + */ + private final List<String> values; + + public TaskAttribute(TaskAttribute parentAttribute, String attributeId) { + Assert.isNotNull(parentAttribute); + Assert.isNotNull(attributeId); + this.parentAttribute = parentAttribute; + this.attributeId = attributeId.intern(); + this.taskData = parentAttribute.getTaskData(); + this.values = new ArrayList<String>(1); + parentAttribute.add(this); + } + + /** + * Constructor for the root node. + */ + TaskAttribute(TaskData taskData) { + Assert.isNotNull(taskData); + this.parentAttribute = null; + this.taskData = taskData; + this.attributeId = "root"; //$NON-NLS-1$ + this.values = new ArrayList<String>(1); + } + + private void add(TaskAttribute attribute) { + if (attributeById == null) { + attributeById = new LinkedHashMap<String, TaskAttribute>(); + } + attributeById.put(attribute.getId(), attribute); + } + + public void addValue(String value) { + Assert.isNotNull(value); + values.add(value); + } + + public void clearAttributes() { + attributeById = null; + } + + void clearMetaDataMap() { + metaData = null; + } + + public void clearOptions() { + optionByKey = null; + } + + public void clearValues() { + values.clear(); + } + + public TaskAttribute createAttribute(String attributeId) { + return new TaskAttribute(this, attributeId); + } + + public void deepAddCopy(TaskAttribute source) { + TaskAttribute target = createAttribute(source.getId()); + target.values.addAll(source.values); + if (source.metaData != null) { + target.metaData = new LinkedHashMap<String, String>(source.metaData); + } + if (source.optionByKey != null) { + target.optionByKey = new LinkedHashMap<String, String>(source.optionByKey); + } + if (source.attributeById != null) { + for (TaskAttribute child : source.attributeById.values()) { + target.deepAddCopy(child); + } + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final TaskAttribute other = (TaskAttribute) obj; + if (attributeId == null) { + if (other.attributeId != null) { + return false; + } + } else if (!attributeId.equals(other.attributeId)) { + return false; + } + return true; + } + + public TaskAttribute getAttribute(String attributeId) { + Assert.isNotNull(attributeId); + return (attributeById != null) ? attributeById.get(attributeId) : null; + } + + public Map<String, TaskAttribute> getAttributes() { + if (attributeById != null) { + return Collections.unmodifiableMap(attributeById); + } else { + return Collections.emptyMap(); + } + } + + public String getId() { + return attributeId; + } + + public TaskAttribute getMappedAttribute(String attributeId) { + Assert.isNotNull(attributeId); + return (attributeById != null) ? attributeById.get(getTaskData().getAttributeMapper().mapToRepositoryKey(this, + attributeId)) : null; + } + + public TaskAttribute getMappedAttribute(String[] path) { + TaskAttribute attribute = this; + for (String id : path) { + attribute = attribute.getMappedAttribute(id); + if (attribute == null) { + break; + } + } + return attribute; + } + + String getMetaDatum(String key) { + return (metaData != null) ? metaData.get(key) : null; + } + + Map<String, String> getMetaDataMap() { + if (metaData != null) { + return Collections.unmodifiableMap(metaData); + } else { + return Collections.emptyMap(); + } + } + + public String getOption(String key) { + return (optionByKey != null) ? optionByKey.get(key) : null; + } + + public Map<String, String> getOptions() { + if (optionByKey != null) { + return Collections.unmodifiableMap(optionByKey); + } else { + return Collections.emptyMap(); + } + } + + public TaskAttribute getParentAttribute() { + return parentAttribute; + } + + public String[] getPath() { + List<String> path = new ArrayList<String>(); + TaskAttribute attribute = this; + while (attribute.getParentAttribute() != null) { + path.add(attribute.getId()); + attribute = attribute.getParentAttribute(); + } + Collections.reverse(path); + return path.toArray(new String[0]); + } + + public TaskAttributeMetaData getMetaData() { + return new TaskAttributeMetaData(this); + } + + public TaskData getTaskData() { + return taskData; + } + + /** + * @return empty String if not available + */ + public String getValue() { + if (values.size() > 0) { + return values.get(0); + } else { + return ""; //$NON-NLS-1$ + } + } + + public List<String> getValues() { + return Collections.unmodifiableList(values); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((attributeId == null) ? 0 : attributeId.hashCode()); + return result; + } + + void putMetaDatum(String key, String value) { + Assert.isNotNull(key); + Assert.isNotNull(value); + if (metaData == null) { + metaData = new LinkedHashMap<String, String>(); + } + metaData.put(key, value); + } + + /** + * Adds an attribute option value + * + * @param readableValue + * The value displayed on the screen + * @param parameterValue + * The option value used when sending the form to the server + */ + public void putOption(String key, String value) { + Assert.isNotNull(key); + Assert.isNotNull(value); + if (optionByKey == null) { + optionByKey = new LinkedHashMap<String, String>(); + } + optionByKey.put(key, value); + } + + public void removeAttribute(String attributeId) { + if (attributeById != null) { + attributeById.remove(attributeId); + } + } + + void removeMetaDatum(String metaDataId) { + if (metaData != null) { + metaData.remove(metaDataId); + } + } + + public void removeValue(String value) { + values.remove(value); + } + + public void setValue(String value) { + Assert.isNotNull(value); + if (values.size() > 0) { + values.clear(); + } + values.add(value); + } + + public void setValues(List<String> values) { + this.values.clear(); + this.values.addAll(values); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + toString(sb, ""); //$NON-NLS-1$ + return sb.toString(); + } + + private void toString(StringBuilder sb, String prefix) { + sb.append(prefix); + sb.append("TaskAttribute[id="); //$NON-NLS-1$ + sb.append(attributeId); + sb.append(",values="); //$NON-NLS-1$ + sb.append(values); + sb.append(",options="); //$NON-NLS-1$ + sb.append(optionByKey); + sb.append(",metaData="); //$NON-NLS-1$ + sb.append(metaData); + sb.append("]"); //$NON-NLS-1$ + if (attributeById != null) { + for (TaskAttribute child : attributeById.values()) { + sb.append("\n"); //$NON-NLS-1$ + child.toString(sb, prefix + " "); //$NON-NLS-1$ + } + } + } + + public TaskAttribute createMappedAttribute(String attributeId) { + Assert.isNotNull(attributeId); + String mappedAttributeId = getTaskData().getAttributeMapper().mapToRepositoryKey(this, attributeId); + Assert.isNotNull(mappedAttributeId); + return new TaskAttribute(this, mappedAttributeId); + } +} diff --git a/org.eclipse.mylyn.tasks.index.core/src/org/eclipse/mylyn/internal/tasks/index/core/TaskListIndex.java b/org.eclipse.mylyn.tasks.index.core/src/org/eclipse/mylyn/internal/tasks/index/core/TaskListIndex.java index cdefbfdbf..0d903eb97 100644 --- a/org.eclipse.mylyn.tasks.index.core/src/org/eclipse/mylyn/internal/tasks/index/core/TaskListIndex.java +++ b/org.eclipse.mylyn.tasks.index.core/src/org/eclipse/mylyn/internal/tasks/index/core/TaskListIndex.java @@ -1,1202 +1,1202 @@ -/*******************************************************************************
- * Copyright (c) 2011, 2012 Tasktop Technologies.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Tasktop Technologies - initial API and implementation
- *******************************************************************************/
-
-package org.eclipse.mylyn.internal.tasks.index.core;
-
-import static org.eclipse.mylyn.tasks.core.data.TaskAttribute.META_INDEXED_AS_CONTENT;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.logging.Logger;
-
-import org.apache.lucene.document.DateTools;
-import org.apache.lucene.document.DateTools.Resolution;
-import org.apache.lucene.document.Document;
-import org.apache.lucene.document.Field;
-import org.apache.lucene.document.Field.Store;
-import org.apache.lucene.index.CorruptIndexException;
-import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.IndexWriter;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.queryParser.ParseException;
-import org.apache.lucene.queryParser.QueryParser;
-import org.apache.lucene.search.BooleanClause;
-import org.apache.lucene.search.BooleanClause.Occur;
-import org.apache.lucene.search.BooleanQuery;
-import org.apache.lucene.search.IndexSearcher;
-import org.apache.lucene.search.PrefixQuery;
-import org.apache.lucene.search.Query;
-import org.apache.lucene.search.ScoreDoc;
-import org.apache.lucene.search.TermQuery;
-import org.apache.lucene.search.TopDocs;
-import org.apache.lucene.store.Directory;
-import org.apache.lucene.store.LockObtainFailedException;
-import org.apache.lucene.store.NIOFSDirectory;
-import org.apache.lucene.util.Version;
-import org.eclipse.core.runtime.Assert;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.MultiStatus;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.core.runtime.Platform;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.core.runtime.SubMonitor;
-import org.eclipse.core.runtime.jobs.Job;
-import org.eclipse.mylyn.commons.core.StatusHandler;
-import org.eclipse.mylyn.internal.tasks.core.AbstractTask;
-import org.eclipse.mylyn.internal.tasks.core.ITaskList;
-import org.eclipse.mylyn.internal.tasks.core.ITaskListChangeListener;
-import org.eclipse.mylyn.internal.tasks.core.ITaskListRunnable;
-import org.eclipse.mylyn.internal.tasks.core.TaskComment;
-import org.eclipse.mylyn.internal.tasks.core.TaskContainerDelta;
-import org.eclipse.mylyn.internal.tasks.core.TaskList;
-import org.eclipse.mylyn.internal.tasks.core.data.ITaskDataManagerListener;
-import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManager;
-import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManagerEvent;
-import org.eclipse.mylyn.tasks.core.IRepositoryElement;
-import org.eclipse.mylyn.tasks.core.IRepositoryListener;
-import org.eclipse.mylyn.tasks.core.IRepositoryManager;
-import org.eclipse.mylyn.tasks.core.IRepositoryPerson;
-import org.eclipse.mylyn.tasks.core.ITask;
-import org.eclipse.mylyn.tasks.core.TaskRepository;
-import org.eclipse.mylyn.tasks.core.data.AbstractTaskSchema;
-import org.eclipse.mylyn.tasks.core.data.DefaultTaskSchema;
-import org.eclipse.mylyn.tasks.core.data.ITaskDataManager;
-import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
-import org.eclipse.mylyn.tasks.core.data.TaskData;
-
-/**
- * An index on a task list, provides a way to {@link #find(String, TaskCollector, int) search for tasks}, and a way to
- * {@link #matches(ITask, String) match tasks}. Tasks are matched against a search query.
- * <p>
- * The task list has a configurable delay before it updates, meaning that there is a period of time where the index will
- * be out of date with respect to task changes. The idea is that updates to the index can be "batched" for greater
- * efficiency. Additionally, it's possible for a task to be updated either before or after the index is added to the
- * task list as a listener, thus opening the possibility of changes without updates to the index. In either of these
- * cases, the index can be out of date with respect to the current state of tasks. If the index is used in such a state,
- * the result could be either false matches, no match where there should be a match, or incorrect prioritization of
- * index "hits".
- * </p>
- * <p>
- * The index has the option of reindexing all tasks via API. This will bring the index up to date and is useful for
- * cases where it's known that the index may not be up to date. In its current form this reindex operation can be
- * triggered by the user by including "index:reset" in the search string. Reindexing is potentially an expensive, IO
- * intensive long-running operation. With about 20,000 tasks in my task list and an SSD, reindexing takes about 90
- * seconds.
- * </p>
- *
- * @author David Green
- */
-public class TaskListIndex implements ITaskDataManagerListener, ITaskListChangeListener, IRepositoryListener {
-
- private class MaintainIndexJob extends Job {
-
- public MaintainIndexJob() {
- super(Messages.TaskListIndex_indexerJob);
- setUser(false);
- setSystem(false); // true?
- setPriority(Job.LONG);
- }
-
- @Override
- public IStatus run(IProgressMonitor m) {
- if (m.isCanceled()) {
- return Status.CANCEL_STATUS;
- }
- try {
- maintainIndex(m);
- } catch (CoreException e) {
- MultiStatus logStatus = new MultiStatus(TasksIndexCore.ID_PLUGIN, 0,
- "Failed to update task list index", e); //$NON-NLS-1$
- logStatus.add(e.getStatus());
- StatusHandler.log(logStatus);
- }
- return Status.OK_STATUS;
- }
-
- }
-
- public abstract static class TaskCollector {
-
- public abstract void collect(ITask task);
-
- }
-
- private static final Object COMMAND_RESET_INDEX = "index:reset"; //$NON-NLS-1$
-
- public static enum IndexField {
- IDENTIFIER(false, null, TaskAttribute.TYPE_SHORT_TEXT), //
- TASK_KEY(false, DefaultTaskSchema.getInstance().TASK_KEY), //
- REPOSITORY_URL(false, null, TaskAttribute.TYPE_URL), //
- SUMMARY(true, DefaultTaskSchema.getInstance().SUMMARY), //
- CONTENT(true, null, TaskAttribute.TYPE_LONG_TEXT), //
- ASSIGNEE(true, DefaultTaskSchema.getInstance().USER_ASSIGNED), //
- REPORTER(true, DefaultTaskSchema.getInstance().USER_REPORTER), //
- PERSON(true, null, TaskAttribute.TYPE_PERSON), //
- COMPONENT(true, DefaultTaskSchema.getInstance().COMPONENT), //
- COMPLETION_DATE(true, DefaultTaskSchema.getInstance().DATE_COMPLETION), //
- CREATION_DATE(true, DefaultTaskSchema.getInstance().DATE_CREATION), //
- DUE_DATE(true, DefaultTaskSchema.getInstance().DATE_DUE), //
- MODIFICATION_DATE(true, DefaultTaskSchema.getInstance().DATE_MODIFICATION), //
- DESCRIPTION(true, DefaultTaskSchema.getInstance().DESCRIPTION), //
- KEYWORDS(true, DefaultTaskSchema.getInstance().KEYWORDS), //
- PRODUCT(true, DefaultTaskSchema.getInstance().PRODUCT), //
- RESOLUTION(true, DefaultTaskSchema.getInstance().RESOLUTION), //
- SEVERITY(true, DefaultTaskSchema.getInstance().SEVERITY), //
- STATUS(true, DefaultTaskSchema.getInstance().STATUS);
-
- private final String attributeId;
-
- private final String type;
-
- private final boolean userVisible;
-
- private IndexField(boolean userVisible, AbstractTaskSchema.Field field) {
- this(userVisible, field.getKey(), field.getType());
- }
-
- private IndexField(boolean userVisible, String attributeId, String type) {
- this.userVisible = userVisible;
- this.attributeId = attributeId;
- this.type = type;
- }
-
- public String fieldName() {
- return name().toLowerCase();
- }
-
- /**
- * get the task attribute id, or null if this field has special handling
- */
- public String getAttributeId() {
- return attributeId;
- }
-
- /**
- * indicate if the field should be exposed in the UI
- */
- public boolean isUserVisible() {
- return userVisible;
- }
-
- /**
- * the type of field, as it relates to <code>TaskAttribute.TYPE_*</code>
- */
- public String getType() {
- return type;
- }
-
- /**
- * indicate if the field is a date or date/time field
- */
- public boolean isTypeDate() {
- return type != null && (TaskAttribute.TYPE_DATE.equals(type) || TaskAttribute.TYPE_DATETIME.equals(type));
- }
-
- /**
- * indicate if this is a person field
- */
- public boolean isPersonField() {
- return type != null && (TaskAttribute.TYPE_PERSON.equals(type) || TaskAttribute.TYPE_PERSON.equals(type));
- }
-
- public static IndexField fromFieldName(String fieldName) {
- try {
- return IndexField.valueOf(fieldName.toUpperCase());
- } catch (IllegalArgumentException e) {
- return null;
- }
- }
-
- }
-
- private static enum MaintainIndexType {
- STARTUP, REINDEX
- }
-
- // FIXME: document concurrency model
-
- private Directory directory;
-
- private MaintainIndexJob maintainIndexJob;
-
- /**
- * must be synchronized before accessing or modifying
- */
- private final Map<ITask, TaskData> reindexQueue = new HashMap<ITask, TaskData>();
-
- /**
- * do not access directly, instead use {@link #getIndexReader()}. 'this' must be synchronized before accessing or
- * modifying
- */
- private IndexReader indexReader;
-
- /**
- * indicate the need to rebuild the whole index
- */
- private volatile boolean rebuildIndex = false;
-
- /**
- * 'this' must be synchronized before accessing or modifying
- */
- private String lastPatternString;
-
- /**
- * 'this' must be synchronized before accessing or modifying
- */
- private Set<String> lastResults;
-
- private IndexField defaultField = IndexField.SUMMARY;
-
- private final TaskList taskList;
-
- private final TaskDataManager dataManager;
-
- private final IRepositoryManager repositoryManager;
-
- private long startupDelay = 6000L;
-
- private long reindexDelay = 3000L;
-
- private int maxMatchSearchHits = 1500;
-
- /**
- * must hold this lock as a read lock when accessing the index, and must hold this lock as a write lock when closing
- * or reassigning {@link #indexReader}.
- */
- private final ReadWriteLock indexReaderLock = new ReentrantReadWriteLock(true);
-
- private TaskListIndex(TaskList taskList, TaskDataManager dataManager, IRepositoryManager repositoryManager) {
- Assert.isNotNull(taskList);
- Assert.isNotNull(dataManager);
- Assert.isNotNull(repositoryManager);
-
- this.taskList = taskList;
- this.dataManager = dataManager;
- this.repositoryManager = repositoryManager;
- }
-
- /**
- * the task list associated with this index
- */
- public ITaskList getTaskList() {
- return taskList;
- }
-
- /**
- * the data manager associated with this index
- */
- public ITaskDataManager getDataManager() {
- return dataManager;
- }
-
- /**
- * the repository manager associated with this index
- */
- public IRepositoryManager getRepositoryManager() {
- return repositoryManager;
- }
-
- /**
- * Create an index on the given task list. Must be matched by a corresponding call to {@link #close()}.
- *
- * @param taskList
- * the task list that is to be indexed
- * @param dataManager
- * the data manager that corresponds to the task list
- * @param repositoryManager
- * the repository manager that corresponds to the task list
- * @param indexLocation
- * the location of the index on the filesystem
- * @see #TaskListIndex(TaskList, TaskDataManager, Directory)
- */
- public TaskListIndex(TaskList taskList, TaskDataManager dataManager, IRepositoryManager repositoryManager,
- File indexLocation) {
- this(taskList, dataManager, repositoryManager, indexLocation, 6000L);
- }
-
- /**
- * Create an index on the given task list. Must be matched by a corresponding call to {@link #close()}.
- *
- * @param taskList
- * the task list that is to be indexed
- * @param dataManager
- * the data manager that corresponds to the task list
- * @param repositoryManager
- * the repository manager that corresponds to the task list
- * @param startupDelay
- * the delay in miliseconds before the index initialization maintenance process should begin
- * @see #TaskListIndex(TaskList, TaskDataManager, File)
- */
- public TaskListIndex(TaskList taskList, TaskDataManager dataManager, IRepositoryManager repositoryManager,
- File indexLocation, long startupDelay) {
- this(taskList, dataManager, repositoryManager);
- Assert.isTrue(startupDelay >= 0L && startupDelay <= (1000L * 60));
- Assert.isNotNull(indexLocation);
-
- this.startupDelay = startupDelay;
- if (!indexLocation.exists()) {
- rebuildIndex = true;
- if (!indexLocation.mkdirs()) {
- StatusHandler.log(new Status(IStatus.ERROR, TasksIndexCore.ID_PLUGIN,
- "Cannot create task list index folder: " + indexLocation)); //$NON-NLS-1$
- }
- }
- if (indexLocation.exists() && indexLocation.isDirectory()) {
- try {
- directory = new NIOFSDirectory(indexLocation);
- } catch (IOException e) {
- StatusHandler.log(new Status(IStatus.ERROR, TasksIndexCore.ID_PLUGIN,
- "Cannot create task list index", e)); //$NON-NLS-1$
- }
- }
- initialize();
- }
-
- /**
- * Create an index on the given task list. Must be matched by a corresponding call to {@link #close()}.
- *
- * @param taskList
- * the task list that is to be indexed
- * @param dataManager
- * the data manager that corresponds to the task list
- * @param repositoryManager
- * the repository manager that corresponds to the task list
- * @param directory
- * the directory in which the index should be stored
- * @see #TaskListIndex(TaskList, TaskDataManager, File)
- */
- public TaskListIndex(TaskList taskList, TaskDataManager dataManager, IRepositoryManager repositoryManager,
- Directory directory) {
- this(taskList, dataManager, repositoryManager);
- this.directory = directory;
- initialize();
- }
-
- /**
- * the delay before reindexing occurs after a task has changed or after {@link #reindex()} is called
- */
- public long getReindexDelay() {
- return reindexDelay;
- }
-
- /**
- * the delay before reindexing occurs after a task has changed or after {@link #reindex()} is called.
- *
- * @param reindexDelay
- * The delay in miliseconds. Specify 0 to indicate no delay.
- */
- public void setReindexDelay(long reindexDelay) {
- Assert.isTrue(reindexDelay >= 0);
- this.reindexDelay = reindexDelay;
- }
-
- /**
- * the default field used to match tasks when unspecified in the query
- */
- public IndexField getDefaultField() {
- return defaultField;
- }
-
- /**
- * the default field used to match tasks when unspecified in the query
- */
- public void setDefaultField(IndexField defaultField) {
- Assert.isNotNull(defaultField);
- this.defaultField = defaultField;
- synchronized (this) {
- lastResults = null;
- }
- }
-
- /**
- * the maximum number of search hits that should be provided when using {@link #matches(ITask, String)}
- */
- public int getMaxMatchSearchHits() {
- return maxMatchSearchHits;
- }
-
- /**
- * the maximum number of search hits that should be provided when using {@link #matches(ITask, String)}
- */
- public void setMaxMatchSearchHits(int maxMatchSearchHits) {
- this.maxMatchSearchHits = maxMatchSearchHits;
- }
-
- private void initialize() {
- if (!rebuildIndex) {
- IndexReader indexReader = null;
- try {
- indexReader = getIndexReader();
- } catch (Exception e) {
- // ignore, this can happen if the index is corrupt
- }
- if (indexReader == null) {
- rebuildIndex = true;
- }
- }
- maintainIndexJob = new MaintainIndexJob();
- dataManager.addListener(this);
- taskList.addChangeListener(this);
- repositoryManager.addListener(this);
-
- scheduleIndexMaintenance(MaintainIndexType.STARTUP);
- }
-
- private void scheduleIndexMaintenance(MaintainIndexType type) {
- long delay = 0L;
- switch (type) {
- case STARTUP:
- delay = startupDelay;
- break;
- case REINDEX:
- delay = reindexDelay;
- }
-
- if (delay == 0L) {
- // primarily for testing purposes
-
- maintainIndexJob.cancel();
- try {
- maintainIndexJob.join();
- } catch (InterruptedException e) {
- // ignore
- }
- try {
- maintainIndex(new NullProgressMonitor());
- } catch (CoreException e) {
- MultiStatus logStatus = new MultiStatus(TasksIndexCore.ID_PLUGIN, 0,
- "Failed to update task list index", e); //$NON-NLS-1$
- logStatus.add(e.getStatus());
- StatusHandler.log(logStatus);
- }
- } else {
- maintainIndexJob.schedule(delay);
- }
- }
-
- /**
- * Indicates if the given task matches the given pattern string. Uses the backing index to detect a match by looking
- * for tasks that match the given pattern string. The results of the search are cached such that future calls to
- * this method using the same pattern string do not require use of the backing index, making this method very
- * efficient for multiple calls with the same pattern string. Cached results for a given pattern string are
- * discarded if this method is called with a different pattern string.
- *
- * @param task
- * the task to match
- * @param patternString
- * the pattern used to detect a match
- */
- public boolean matches(ITask task, String patternString) {
- if (patternString.equals(COMMAND_RESET_INDEX)) {
- reindex();
- }
- Lock readLock = indexReaderLock.readLock();
- readLock.lock();
- try {
-
- IndexReader indexReader = getIndexReader();
- if (indexReader != null) {
- Set<String> hits;
-
- final boolean needIndexHit;
- synchronized (this) {
- needIndexHit = lastResults == null
- || (lastPatternString == null || !lastPatternString.equals(patternString));
- }
- if (needIndexHit) {
- this.lastPatternString = patternString;
-
- hits = new HashSet<String>();
-
- IndexSearcher indexSearcher = new IndexSearcher(indexReader);
- try {
- Query query = computeQuery(patternString);
- TopDocs results = indexSearcher.search(query, maxMatchSearchHits);
- for (ScoreDoc scoreDoc : results.scoreDocs) {
- Document document = indexReader.document(scoreDoc.doc);
- hits.add(document.get(IndexField.IDENTIFIER.fieldName()));
- }
- } catch (IOException e) {
- StatusHandler.log(new Status(IStatus.ERROR, TasksIndexCore.ID_PLUGIN,
- "Unexpected failure within task list index", e)); //$NON-NLS-1$
- } finally {
- try {
- indexSearcher.close();
- } catch (IOException e) {
- // ignore
- }
- }
-
- } else {
- hits = lastResults;
- }
- synchronized (this) {
- if (this.indexReader == indexReader) {
- this.lastPatternString = patternString;
- this.lastResults = hits;
- }
- }
- String taskIdentifier = task.getHandleIdentifier();
- return hits != null && hits.contains(taskIdentifier);
- }
-
- } finally {
- readLock.unlock();
- }
- return false;
- }
-
- public void reindex() {
- rebuildIndex = true;
- scheduleIndexMaintenance(MaintainIndexType.REINDEX);
- }
-
- /**
- * call to wait until index maintenance has completed
- *
- * @throws InterruptedException
- */
- public void waitUntilIdle() throws InterruptedException {
- if (!Platform.isRunning() && reindexDelay != 0L) {
- // job join() behaviour is not the same when platform is not running
- Logger.getLogger(TaskListIndex.class.getName()).warning(
- "Index job joining may not work properly when Eclipse platform is not running"); //$NON-NLS-1$
- }
- maintainIndexJob.join();
- }
-
- /**
- * finds tasks that match the given pattern string
- *
- * @param patternString
- * the pattern string, used to match tasks
- * @param collector
- * the collector that receives tasks
- * @param resultsLimit
- * the maximum number of tasks to find. Specifying a limit enables the index to be more efficient since
- * it can skip over matching tasks that do not score highly enough. Specify {@link Integer#MAX_VALUE} if
- * there should be no limit.
- */
- public void find(String patternString, TaskCollector collector, int resultsLimit) {
- Assert.isNotNull(patternString);
- Assert.isNotNull(collector);
- Assert.isTrue(resultsLimit > 0);
-
- Lock readLock = indexReaderLock.readLock();
- readLock.lock();
- try {
- IndexReader indexReader = getIndexReader();
- if (indexReader != null) {
- IndexSearcher indexSearcher = new IndexSearcher(indexReader);
- try {
- Query query = computeQuery(patternString);
- TopDocs results = indexSearcher.search(query, resultsLimit);
- for (ScoreDoc scoreDoc : results.scoreDocs) {
- Document document = indexReader.document(scoreDoc.doc);
- String taskIdentifier = document.get(IndexField.IDENTIFIER.fieldName());
- AbstractTask task = taskList.getTask(taskIdentifier);
- if (task != null) {
- collector.collect(task);
- }
- }
- } catch (IOException e) {
- StatusHandler.log(new Status(IStatus.ERROR, TasksIndexCore.ID_PLUGIN,
- "Unexpected failure within task list index", e)); //$NON-NLS-1$
- } finally {
- try {
- indexSearcher.close();
- } catch (IOException e) {
- // ignore
- }
- }
- }
- } finally {
- readLock.unlock();
- }
- }
-
- private Query computeQuery(String patternString) {
- String upperPatternString = patternString.toUpperCase();
-
- boolean hasBooleanSpecifiers = upperPatternString.contains(" OR ") || upperPatternString.contains(" AND ") //$NON-NLS-1$ //$NON-NLS-2$
- || upperPatternString.contains(" NOT "); //$NON-NLS-1$
-
- if (patternString.indexOf(':') == -1 && !hasBooleanSpecifiers && defaultField == IndexField.SUMMARY
- && patternString.indexOf('"') == -1) {
- return new PrefixQuery(new Term(defaultField.fieldName(), patternString));
- }
- QueryParser qp = new QueryParser(Version.LUCENE_CURRENT, defaultField.fieldName(), new TaskAnalyzer());
- Query q;
- try {
- q = qp.parse(patternString);
- } catch (ParseException e) {
- return new PrefixQuery(new Term(defaultField.fieldName(), patternString));
- }
-
- // relax term clauses to be prefix clauses so that we get results close
- // to what we're expecting
- // from previous task list search
- if (q instanceof BooleanQuery) {
- BooleanQuery query = (BooleanQuery) q;
- for (BooleanClause clause : query.getClauses()) {
- if (clause.getQuery() instanceof TermQuery) {
- TermQuery termQuery = (TermQuery) clause.getQuery();
- clause.setQuery(new PrefixQuery(termQuery.getTerm()));
- }
- if (!hasBooleanSpecifiers) {
- clause.setOccur(Occur.MUST);
- }
- }
- } else if (q instanceof TermQuery) {
- return new PrefixQuery(((TermQuery) q).getTerm());
- }
- return q;
- }
-
- public void close() {
- dataManager.removeListener(this);
- taskList.removeChangeListener(this);
- repositoryManager.removeListener(this);
-
- maintainIndexJob.cancel();
- try {
- maintainIndexJob.join();
- } catch (InterruptedException e) {
- // ignore
- }
-
- Lock writeLock = indexReaderLock.writeLock();
- writeLock.lock();
- try {
- synchronized (this) {
- if (indexReader != null) {
- try {
- indexReader.close();
- } catch (IOException e) {
- // ignore
- }
- indexReader = null;
- }
- }
- try {
- directory.close();
- } catch (IOException e) {
- StatusHandler.log(new Status(IStatus.ERROR, TasksIndexCore.ID_PLUGIN,
- "Cannot close index: " + e.getMessage(), e)); //$NON-NLS-1$
- }
- } finally {
- writeLock.unlock();
- }
- }
-
- private IndexReader getIndexReader() {
- try {
- synchronized (this) {
- if (indexReader == null) {
- indexReader = IndexReader.open(directory, true);
- lastResults = null;
- }
- return indexReader;
- }
- } catch (CorruptIndexException e) {
- rebuildIndex = true;
- if (maintainIndexJob != null) {
- scheduleIndexMaintenance(MaintainIndexType.REINDEX);
- }
- } catch (FileNotFoundException e) {
- rebuildIndex = true;
- // expected if the index doesn't exist
- } catch (IOException e) {
- // ignore
- }
- return null;
- }
-
- public void taskDataUpdated(TaskDataManagerEvent event) {
- reindex(event.getTask(), event.getTaskData());
- }
-
- public void editsDiscarded(TaskDataManagerEvent event) {
- reindex(event.getTask(), event.getTaskData());
- }
-
- public void containersChanged(Set<TaskContainerDelta> containers) {
- for (TaskContainerDelta delta : containers) {
- switch (delta.getKind()) {
- case ADDED:
- case REMOVED:
- case CONTENT:
- IRepositoryElement element = delta.getElement();
- if (element instanceof ITask) {
- ITask task = (ITask) element;
- if ("local".equals(((AbstractTask) task).getConnectorKind())) { //$NON-NLS-1$
- reindex(task, null);
- }
- }
- }
- }
- }
-
- /**
- * advanced usage: cause the given task to be reindexed using {@link MaintainIndexType#REINDEX reindex scheduling
- * rule}.
- *
- * @param task
- * the task
- * @param taskData
- * the task data, or nul if it's not available
- */
- protected void reindex(ITask task, TaskData taskData) {
- if (task == null) {
- // this can happen when edits are discarded
- return;
- }
- if (!taskIsIndexable(task, taskData)) {
- return;
- }
- synchronized (reindexQueue) {
- reindexQueue.put(task, taskData);
- }
- scheduleIndexMaintenance(MaintainIndexType.REINDEX);
- }
-
- private void addIndexedAttributes(Document document, ITask task, TaskAttribute root) {
- addIndexedAttribute(document, IndexField.TASK_KEY, task.getTaskKey());
- addIndexedAttribute(document, IndexField.REPOSITORY_URL, task.getRepositoryUrl());
- addIndexedAttribute(document, IndexField.SUMMARY, task.getSummary());
-
- for (TaskAttribute contentAttribute : computeContentAttributes(root)) {
- addIndexedAttribute(document, IndexField.CONTENT, contentAttribute);
- }
-
- addIndexedDateAttributes(document, task);
-
- List<TaskAttribute> commentAttributes = root.getTaskData()
- .getAttributeMapper()
- .getAttributesByType(root.getTaskData(), TaskAttribute.TYPE_COMMENT);
- for (TaskAttribute commentAttribute : commentAttributes) {
-
- TaskComment taskComment = new TaskComment(root.getTaskData().getAttributeMapper().getTaskRepository(),
- task, commentAttribute);
- root.getTaskData().getAttributeMapper().updateTaskComment(taskComment, commentAttribute);
-
- String text = taskComment.getText();
- if (text.length() != 0) {
- addIndexedAttribute(document, IndexField.CONTENT, text);
- }
- IRepositoryPerson author = taskComment.getAuthor();
- if (author != null) {
- addIndexedAttribute(document, IndexField.PERSON, author.getPersonId());
- }
- }
-
- List<TaskAttribute> personAttributes = root.getTaskData()
- .getAttributeMapper()
- .getAttributesByType(root.getTaskData(), TaskAttribute.TYPE_PERSON);
- for (TaskAttribute personAttribute : personAttributes) {
- addIndexedAttribute(document, IndexField.PERSON, personAttribute);
- }
-
- for (IndexField field : IndexField.values()) {
- if (field.getAttributeId() != null) {
- addIndexedAttribute(document, field, root.getMappedAttribute(field.getAttributeId()));
- }
- }
- }
-
- /**
- * compute attributes that should be indexed as {@link IndexField#CONTENT}
- */
- private Collection<TaskAttribute> computeContentAttributes(TaskAttribute root) {
- Set<TaskAttribute> attributes = new LinkedHashSet<TaskAttribute>();
-
- // add default content attributes
- {
- TaskAttribute attribute = root.getMappedAttribute(TaskAttribute.SUMMARY);
- if (attribute != null) {
- attributes.add(attribute);
- }
- attribute = root.getMappedAttribute(TaskAttribute.DESCRIPTION);
- if (attribute != null) {
- attributes.add(attribute);
- }
- }
-
- for (TaskAttribute attribute : root.getAttributes().values()) {
- if (Boolean.parseBoolean(attribute.getMetaData().getValue(META_INDEXED_AS_CONTENT))) {
- attributes.add(attribute);
- }
- }
-
- return attributes;
- }
-
- private void addIndexedAttributes(Document document, ITask task) {
- addIndexedAttribute(document, IndexField.TASK_KEY, task.getTaskKey());
- addIndexedAttribute(document, IndexField.REPOSITORY_URL, task.getRepositoryUrl());
- addIndexedAttribute(document, IndexField.SUMMARY, task.getSummary());
- addIndexedAttribute(document, IndexField.CONTENT, task.getSummary());
- addIndexedAttribute(document, IndexField.CONTENT, ((AbstractTask) task).getNotes());
- addIndexedDateAttributes(document, task);
- }
-
- private void addIndexedDateAttributes(Document document, ITask task) {
- addIndexedAttribute(document, IndexField.COMPLETION_DATE, task.getCompletionDate());
- addIndexedAttribute(document, IndexField.CREATION_DATE, task.getCreationDate());
- addIndexedAttribute(document, IndexField.DUE_DATE, task.getDueDate());
- addIndexedAttribute(document, IndexField.MODIFICATION_DATE, task.getModificationDate());
- }
-
- private void addIndexedAttribute(Document document, IndexField indexField, TaskAttribute attribute) {
- if (attribute == null) {
- return;
- }
- List<String> values = attribute.getTaskData().getAttributeMapper().getValueLabels(attribute);
- if (values.isEmpty()) {
- return;
- }
-
- if (indexField.isPersonField()) {
- IRepositoryPerson repositoryPerson = attribute.getTaskData()
- .getAttributeMapper()
- .getRepositoryPerson(attribute);
- addIndexedAttribute(document, indexField, repositoryPerson);
-
- if (values.size() <= 1) {
- return;
- }
- }
-
- for (String value : values) {
- if (value.length() != 0) {
- addIndexedAttribute(document, indexField, value);
- }
- }
- }
-
- private void addIndexedAttribute(Document document, IndexField indexField, IRepositoryPerson person) {
- if (person != null) {
- addIndexedAttribute(document, indexField, person.getPersonId());
- addIndexedAttribute(document, indexField, person.getName());
- }
- }
-
- private void addIndexedAttribute(Document document, IndexField indexField, String value) {
- if (value == null) {
- return;
- }
- Field field = document.getField(indexField.fieldName());
- if (field == null) {
- field = new Field(indexField.fieldName(), value, Store.YES, org.apache.lucene.document.Field.Index.ANALYZED);
- document.add(field);
- } else {
- String existingValue = field.stringValue();
- if (indexField != IndexField.PERSON || !existingValue.contains(value)) {
- field.setValue(existingValue + " " + value); //$NON-NLS-1$
- }
- }
- }
-
- private void addIndexedAttribute(Document document, IndexField indexField, Date date) {
- if (date == null) {
- return;
- }
- // FIXME: date tools converts dates to GMT, and we don't really want that. So
- // move the date by the GMT offset if there is any
-
- String value = DateTools.dateToString(date, Resolution.HOUR);
- Field field = document.getField(indexField.fieldName());
- if (field == null) {
- field = new Field(indexField.fieldName(), value, Store.YES, org.apache.lucene.document.Field.Index.ANALYZED);
- document.add(field);
- } else {
- field.setValue(value);
- }
- }
-
- /**
- * Computes a query element for a field that must lie in a specified date range.
- *
- * @param field
- * the field
- * @param lowerBoundInclusive
- * the date lower bound that the field value must match, inclusive
- * @param upperBoundInclusive
- * the date upper bound that the field value must match, inclusive
- * @return
- */
- public String computeQueryFieldDateRange(IndexField field, Date lowerBoundInclusive, Date upperBoundInclusive) {
- return field.fieldName()
- + ":[" + DateTools.dateToString(lowerBoundInclusive, Resolution.DAY) + " TO " + DateTools.dateToString(upperBoundInclusive, Resolution.DAY) + "]"; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
- }
-
- /**
- * Indicates if the given task is indexable. The default implementation returns true, subclasses may override to
- * filter some tasks from the task list. This method may be called more than once per task, with some calls omitting
- * the task data. In this way implementations can avoid loading task data if the decision to filter tasks can be
- * based on the ITask alone. Implementations that must read the task data in order to determine eligibility for
- * indexing should return true for tasks where the provided task data is null.
- *
- * @param task
- * the task
- * @param taskData
- * the task data, or null if there is no task data
- * @return true if the given task should be indexed, otherwise false.
- */
- protected boolean taskIsIndexable(ITask task, TaskData taskData) {
- return true;
- }
-
- /**
- * Escapes special characters in the given literal value so that they are not interpreted as special characters in a
- * query.
- *
- * @param value
- * the value to escape
- * @return a representation of the value with characters escaped
- */
- public String escapeFieldValue(String value) {
- // see http://lucene.apache.org/java/2_9_1/queryparsersyntax.html#Escaping%20Special%20Characters
- String escaped = value.replaceAll("([\\+\\-\\!\\(\\)\\{\\}\\[\\]^\"~\\*\\?:\\\\]|&&|\\|\\|)", "\\\\$1"); //$NON-NLS-1$ //$NON-NLS-2$
- return escaped;
- }
-
- private void maintainIndex(IProgressMonitor m) throws CoreException {
- final int WORK_PER_SEGMENT = 1000;
- SubMonitor monitor = SubMonitor.convert(m, 2 * WORK_PER_SEGMENT);
- try {
- try {
- if (!rebuildIndex) {
- try {
- IndexReader reader = IndexReader.open(directory, false);
- reader.close();
- } catch (CorruptIndexException e) {
- rebuildIndex = true;
- }
- }
-
- if (rebuildIndex) {
- synchronized (reindexQueue) {
- reindexQueue.clear();
- }
-
- IStatus status = rebuildIndexCompletely(monitor.newChild(WORK_PER_SEGMENT));
- if (!status.isOK()) {
- StatusHandler.log(status);
- }
- } else {
- monitor.worked(WORK_PER_SEGMENT);
- }
-
- // index any tasks that have been changed
- indexQueuedTasks(monitor.newChild(WORK_PER_SEGMENT));
-
- // prevent new searches from reading the now-stale index
- closeIndexReader();
- } catch (IOException e) {
- throw new CoreException(new Status(IStatus.ERROR, TasksIndexCore.ID_PLUGIN,
- "Unexpected exception: " + e.getMessage(), e)); //$NON-NLS-1$
- }
- } finally {
- monitor.done();
- }
- }
-
- private void closeIndexReader() throws IOException {
- Lock writeLock = indexReaderLock.writeLock();
- writeLock.lock();
- try {
- synchronized (this) {
- if (indexReader != null) {
- indexReader.close();
- indexReader = null;
- }
- }
- } finally {
- writeLock.unlock();
- }
- }
-
- private void indexQueuedTasks(SubMonitor monitor) throws CorruptIndexException, LockObtainFailedException,
- IOException {
-
- synchronized (reindexQueue) {
- if (reindexQueue.isEmpty()) {
- return;
- }
-
- monitor.beginTask(Messages.TaskListIndex_task_rebuilding_index, reindexQueue.size());
- }
-
- try {
- IndexWriter writer = null;
- try {
- Map<ITask, TaskData> workingQueue = new HashMap<ITask, TaskData>();
-
- // reindex tasks that are in the reindexQueue, making multiple passes so that we catch anything
- // added/changed while we were reindexing
- for (;;) {
- workingQueue.clear();
-
- synchronized (reindexQueue) {
- if (reindexQueue.isEmpty()) {
- break;
- }
- // move items from the reindexQueue to the temporary working queue
- workingQueue.putAll(reindexQueue);
- reindexQueue.keySet().removeAll(workingQueue.keySet());
- }
-
- if (writer == null) {
- writer = new IndexWriter(directory, new TaskAnalyzer(), false,
- IndexWriter.MaxFieldLength.UNLIMITED);
- }
-
- monitor.setWorkRemaining(workingQueue.size());
-
- for (Entry<ITask, TaskData> entry : workingQueue.entrySet()) {
- ITask task = entry.getKey();
- TaskData taskData = entry.getValue();
-
- writer.deleteDocuments(new Term(IndexField.IDENTIFIER.fieldName(), task.getHandleIdentifier()));
-
- add(writer, task, taskData);
-
- monitor.worked(1);
- }
- }
- } finally {
- if (writer != null) {
- writer.close();
- }
- }
- } finally {
- monitor.done();
- }
- }
-
- private class TaskListState implements ITaskListRunnable {
- List<ITask> indexableTasks;
-
- public void execute(IProgressMonitor monitor) throws CoreException {
- Collection<AbstractTask> tasks = taskList.getAllTasks();
- indexableTasks = new ArrayList<ITask>(tasks.size());
-
- for (ITask task : tasks) {
- if (taskIsIndexable(task, null)) {
- indexableTasks.add(task);
- }
- }
- }
-
- }
-
- private IStatus rebuildIndexCompletely(SubMonitor monitor) throws CorruptIndexException, LockObtainFailedException,
- IOException, CoreException {
-
- MultiStatus multiStatus = new MultiStatus(TasksIndexCore.ID_PLUGIN, 0, null, null);
-
- // get indexable tasks from the task list
- final TaskListState taskListState = new TaskListState();
- taskList.run(taskListState, monitor.newChild(0));
-
- monitor.beginTask(Messages.TaskListIndex_task_rebuilding_index, taskListState.indexableTasks.size());
- try {
- final IndexWriter writer = new IndexWriter(directory, new TaskAnalyzer(), true,
- IndexWriter.MaxFieldLength.UNLIMITED);
- try {
-
- for (ITask task : taskListState.indexableTasks) {
- if (taskIsIndexable(task, null)) {
- try {
- TaskData taskData = dataManager.getTaskData(task);
- add(writer, task, taskData);
- } catch (CoreException e) {
- // an individual task data error should not prevent the index from updating
- multiStatus.add(e.getStatus());
- }
- }
- monitor.worked(1);
- }
- synchronized (this) {
- rebuildIndex = false;
- }
- } finally {
- writer.close();
- }
- } finally {
- monitor.done();
- }
- return multiStatus;
- }
-
- /**
- * @param writer
- * @param task
- * the task
- * @param taskData
- * may be null for local tasks
- * @throws CorruptIndexException
- * @throws IOException
- */
- private void add(IndexWriter writer, ITask task, TaskData taskData) throws CorruptIndexException, IOException {
- if (!taskIsIndexable(task, taskData)) {
- return;
- }
-
- Document document = new Document();
-
- document.add(new Field(IndexField.IDENTIFIER.fieldName(), task.getHandleIdentifier(), Store.YES,
- org.apache.lucene.document.Field.Index.ANALYZED));
- if (taskData == null) {
- if ("local".equals(((AbstractTask) task).getConnectorKind())) { //$NON-NLS-1$
- addIndexedAttributes(document, task);
- } else {
- return;
- }
- } else {
- addIndexedAttributes(document, task, taskData.getRoot());
- }
- writer.addDocument(document);
- }
-
- public void repositoryAdded(TaskRepository repository) {
- // ignore
- }
-
- public void repositoryRemoved(TaskRepository repository) {
- // ignore
- }
-
- public void repositorySettingsChanged(TaskRepository repository) {
- // ignore
- }
-
- public void repositoryUrlChanged(TaskRepository repository, String oldUrl) {
- reindex();
- }
-}
+/******************************************************************************* + * Copyright (c) 2011, 2012 Tasktop Technologies. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.index.core; + +import static org.eclipse.mylyn.tasks.core.data.TaskAttribute.META_INDEXED_AS_CONTENT; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Logger; + +import org.apache.lucene.document.DateTools; +import org.apache.lucene.document.DateTools.Resolution; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.Field.Store; +import org.apache.lucene.index.CorruptIndexException; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.Term; +import org.apache.lucene.queryParser.ParseException; +import org.apache.lucene.queryParser.QueryParser; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanClause.Occur; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.PrefixQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.LockObtainFailedException; +import org.apache.lucene.store.NIOFSDirectory; +import org.apache.lucene.util.Version; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.MultiStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.SubMonitor; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.mylyn.commons.core.StatusHandler; +import org.eclipse.mylyn.internal.tasks.core.AbstractTask; +import org.eclipse.mylyn.internal.tasks.core.ITaskList; +import org.eclipse.mylyn.internal.tasks.core.ITaskListChangeListener; +import org.eclipse.mylyn.internal.tasks.core.ITaskListRunnable; +import org.eclipse.mylyn.internal.tasks.core.TaskComment; +import org.eclipse.mylyn.internal.tasks.core.TaskContainerDelta; +import org.eclipse.mylyn.internal.tasks.core.TaskList; +import org.eclipse.mylyn.internal.tasks.core.data.ITaskDataManagerListener; +import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManager; +import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManagerEvent; +import org.eclipse.mylyn.tasks.core.IRepositoryElement; +import org.eclipse.mylyn.tasks.core.IRepositoryListener; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.mylyn.tasks.core.IRepositoryPerson; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskSchema; +import org.eclipse.mylyn.tasks.core.data.DefaultTaskSchema; +import org.eclipse.mylyn.tasks.core.data.ITaskDataManager; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskData; + +/** + * An index on a task list, provides a way to {@link #find(String, TaskCollector, int) search for tasks}, and a way to + * {@link #matches(ITask, String) match tasks}. Tasks are matched against a search query. + * <p> + * The task list has a configurable delay before it updates, meaning that there is a period of time where the index will + * be out of date with respect to task changes. The idea is that updates to the index can be "batched" for greater + * efficiency. Additionally, it's possible for a task to be updated either before or after the index is added to the + * task list as a listener, thus opening the possibility of changes without updates to the index. In either of these + * cases, the index can be out of date with respect to the current state of tasks. If the index is used in such a state, + * the result could be either false matches, no match where there should be a match, or incorrect prioritization of + * index "hits". + * </p> + * <p> + * The index has the option of reindexing all tasks via API. This will bring the index up to date and is useful for + * cases where it's known that the index may not be up to date. In its current form this reindex operation can be + * triggered by the user by including "index:reset" in the search string. Reindexing is potentially an expensive, IO + * intensive long-running operation. With about 20,000 tasks in my task list and an SSD, reindexing takes about 90 + * seconds. + * </p> + * + * @author David Green + */ +public class TaskListIndex implements ITaskDataManagerListener, ITaskListChangeListener, IRepositoryListener { + + private class MaintainIndexJob extends Job { + + public MaintainIndexJob() { + super(Messages.TaskListIndex_indexerJob); + setUser(false); + setSystem(false); // true? + setPriority(Job.LONG); + } + + @Override + public IStatus run(IProgressMonitor m) { + if (m.isCanceled()) { + return Status.CANCEL_STATUS; + } + try { + maintainIndex(m); + } catch (CoreException e) { + MultiStatus logStatus = new MultiStatus(TasksIndexCore.ID_PLUGIN, 0, + "Failed to update task list index", e); //$NON-NLS-1$ + logStatus.add(e.getStatus()); + StatusHandler.log(logStatus); + } + return Status.OK_STATUS; + } + + } + + public abstract static class TaskCollector { + + public abstract void collect(ITask task); + + } + + private static final Object COMMAND_RESET_INDEX = "index:reset"; //$NON-NLS-1$ + + public static enum IndexField { + IDENTIFIER(false, null, TaskAttribute.TYPE_SHORT_TEXT), // + TASK_KEY(false, DefaultTaskSchema.getInstance().TASK_KEY), // + REPOSITORY_URL(false, null, TaskAttribute.TYPE_URL), // + SUMMARY(true, DefaultTaskSchema.getInstance().SUMMARY), // + CONTENT(true, null, TaskAttribute.TYPE_LONG_TEXT), // + ASSIGNEE(true, DefaultTaskSchema.getInstance().USER_ASSIGNED), // + REPORTER(true, DefaultTaskSchema.getInstance().USER_REPORTER), // + PERSON(true, null, TaskAttribute.TYPE_PERSON), // + COMPONENT(true, DefaultTaskSchema.getInstance().COMPONENT), // + COMPLETION_DATE(true, DefaultTaskSchema.getInstance().DATE_COMPLETION), // + CREATION_DATE(true, DefaultTaskSchema.getInstance().DATE_CREATION), // + DUE_DATE(true, DefaultTaskSchema.getInstance().DATE_DUE), // + MODIFICATION_DATE(true, DefaultTaskSchema.getInstance().DATE_MODIFICATION), // + DESCRIPTION(true, DefaultTaskSchema.getInstance().DESCRIPTION), // + KEYWORDS(true, DefaultTaskSchema.getInstance().KEYWORDS), // + PRODUCT(true, DefaultTaskSchema.getInstance().PRODUCT), // + RESOLUTION(true, DefaultTaskSchema.getInstance().RESOLUTION), // + SEVERITY(true, DefaultTaskSchema.getInstance().SEVERITY), // + STATUS(true, DefaultTaskSchema.getInstance().STATUS); + + private final String attributeId; + + private final String type; + + private final boolean userVisible; + + private IndexField(boolean userVisible, AbstractTaskSchema.Field field) { + this(userVisible, field.getKey(), field.getType()); + } + + private IndexField(boolean userVisible, String attributeId, String type) { + this.userVisible = userVisible; + this.attributeId = attributeId; + this.type = type; + } + + public String fieldName() { + return name().toLowerCase(); + } + + /** + * get the task attribute id, or null if this field has special handling + */ + public String getAttributeId() { + return attributeId; + } + + /** + * indicate if the field should be exposed in the UI + */ + public boolean isUserVisible() { + return userVisible; + } + + /** + * the type of field, as it relates to <code>TaskAttribute.TYPE_*</code> + */ + public String getType() { + return type; + } + + /** + * indicate if the field is a date or date/time field + */ + public boolean isTypeDate() { + return type != null && (TaskAttribute.TYPE_DATE.equals(type) || TaskAttribute.TYPE_DATETIME.equals(type)); + } + + /** + * indicate if this is a person field + */ + public boolean isPersonField() { + return type != null && (TaskAttribute.TYPE_PERSON.equals(type) || TaskAttribute.TYPE_PERSON.equals(type)); + } + + public static IndexField fromFieldName(String fieldName) { + try { + return IndexField.valueOf(fieldName.toUpperCase()); + } catch (IllegalArgumentException e) { + return null; + } + } + + } + + private static enum MaintainIndexType { + STARTUP, REINDEX + } + + // FIXME: document concurrency model + + private Directory directory; + + private MaintainIndexJob maintainIndexJob; + + /** + * must be synchronized before accessing or modifying + */ + private final Map<ITask, TaskData> reindexQueue = new HashMap<ITask, TaskData>(); + + /** + * do not access directly, instead use {@link #getIndexReader()}. 'this' must be synchronized before accessing or + * modifying + */ + private IndexReader indexReader; + + /** + * indicate the need to rebuild the whole index + */ + private volatile boolean rebuildIndex = false; + + /** + * 'this' must be synchronized before accessing or modifying + */ + private String lastPatternString; + + /** + * 'this' must be synchronized before accessing or modifying + */ + private Set<String> lastResults; + + private IndexField defaultField = IndexField.SUMMARY; + + private final TaskList taskList; + + private final TaskDataManager dataManager; + + private final IRepositoryManager repositoryManager; + + private long startupDelay = 6000L; + + private long reindexDelay = 3000L; + + private int maxMatchSearchHits = 1500; + + /** + * must hold this lock as a read lock when accessing the index, and must hold this lock as a write lock when closing + * or reassigning {@link #indexReader}. + */ + private final ReadWriteLock indexReaderLock = new ReentrantReadWriteLock(true); + + private TaskListIndex(TaskList taskList, TaskDataManager dataManager, IRepositoryManager repositoryManager) { + Assert.isNotNull(taskList); + Assert.isNotNull(dataManager); + Assert.isNotNull(repositoryManager); + + this.taskList = taskList; + this.dataManager = dataManager; + this.repositoryManager = repositoryManager; + } + + /** + * the task list associated with this index + */ + public ITaskList getTaskList() { + return taskList; + } + + /** + * the data manager associated with this index + */ + public ITaskDataManager getDataManager() { + return dataManager; + } + + /** + * the repository manager associated with this index + */ + public IRepositoryManager getRepositoryManager() { + return repositoryManager; + } + + /** + * Create an index on the given task list. Must be matched by a corresponding call to {@link #close()}. + * + * @param taskList + * the task list that is to be indexed + * @param dataManager + * the data manager that corresponds to the task list + * @param repositoryManager + * the repository manager that corresponds to the task list + * @param indexLocation + * the location of the index on the filesystem + * @see #TaskListIndex(TaskList, TaskDataManager, Directory) + */ + public TaskListIndex(TaskList taskList, TaskDataManager dataManager, IRepositoryManager repositoryManager, + File indexLocation) { + this(taskList, dataManager, repositoryManager, indexLocation, 6000L); + } + + /** + * Create an index on the given task list. Must be matched by a corresponding call to {@link #close()}. + * + * @param taskList + * the task list that is to be indexed + * @param dataManager + * the data manager that corresponds to the task list + * @param repositoryManager + * the repository manager that corresponds to the task list + * @param startupDelay + * the delay in miliseconds before the index initialization maintenance process should begin + * @see #TaskListIndex(TaskList, TaskDataManager, File) + */ + public TaskListIndex(TaskList taskList, TaskDataManager dataManager, IRepositoryManager repositoryManager, + File indexLocation, long startupDelay) { + this(taskList, dataManager, repositoryManager); + Assert.isTrue(startupDelay >= 0L && startupDelay <= (1000L * 60)); + Assert.isNotNull(indexLocation); + + this.startupDelay = startupDelay; + if (!indexLocation.exists()) { + rebuildIndex = true; + if (!indexLocation.mkdirs()) { + StatusHandler.log(new Status(IStatus.ERROR, TasksIndexCore.ID_PLUGIN, + "Cannot create task list index folder: " + indexLocation)); //$NON-NLS-1$ + } + } + if (indexLocation.exists() && indexLocation.isDirectory()) { + try { + directory = new NIOFSDirectory(indexLocation); + } catch (IOException e) { + StatusHandler.log(new Status(IStatus.ERROR, TasksIndexCore.ID_PLUGIN, + "Cannot create task list index", e)); //$NON-NLS-1$ + } + } + initialize(); + } + + /** + * Create an index on the given task list. Must be matched by a corresponding call to {@link #close()}. + * + * @param taskList + * the task list that is to be indexed + * @param dataManager + * the data manager that corresponds to the task list + * @param repositoryManager + * the repository manager that corresponds to the task list + * @param directory + * the directory in which the index should be stored + * @see #TaskListIndex(TaskList, TaskDataManager, File) + */ + public TaskListIndex(TaskList taskList, TaskDataManager dataManager, IRepositoryManager repositoryManager, + Directory directory) { + this(taskList, dataManager, repositoryManager); + this.directory = directory; + initialize(); + } + + /** + * the delay before reindexing occurs after a task has changed or after {@link #reindex()} is called + */ + public long getReindexDelay() { + return reindexDelay; + } + + /** + * the delay before reindexing occurs after a task has changed or after {@link #reindex()} is called. + * + * @param reindexDelay + * The delay in miliseconds. Specify 0 to indicate no delay. + */ + public void setReindexDelay(long reindexDelay) { + Assert.isTrue(reindexDelay >= 0); + this.reindexDelay = reindexDelay; + } + + /** + * the default field used to match tasks when unspecified in the query + */ + public IndexField getDefaultField() { + return defaultField; + } + + /** + * the default field used to match tasks when unspecified in the query + */ + public void setDefaultField(IndexField defaultField) { + Assert.isNotNull(defaultField); + this.defaultField = defaultField; + synchronized (this) { + lastResults = null; + } + } + + /** + * the maximum number of search hits that should be provided when using {@link #matches(ITask, String)} + */ + public int getMaxMatchSearchHits() { + return maxMatchSearchHits; + } + + /** + * the maximum number of search hits that should be provided when using {@link #matches(ITask, String)} + */ + public void setMaxMatchSearchHits(int maxMatchSearchHits) { + this.maxMatchSearchHits = maxMatchSearchHits; + } + + private void initialize() { + if (!rebuildIndex) { + IndexReader indexReader = null; + try { + indexReader = getIndexReader(); + } catch (Exception e) { + // ignore, this can happen if the index is corrupt + } + if (indexReader == null) { + rebuildIndex = true; + } + } + maintainIndexJob = new MaintainIndexJob(); + dataManager.addListener(this); + taskList.addChangeListener(this); + repositoryManager.addListener(this); + + scheduleIndexMaintenance(MaintainIndexType.STARTUP); + } + + private void scheduleIndexMaintenance(MaintainIndexType type) { + long delay = 0L; + switch (type) { + case STARTUP: + delay = startupDelay; + break; + case REINDEX: + delay = reindexDelay; + } + + if (delay == 0L) { + // primarily for testing purposes + + maintainIndexJob.cancel(); + try { + maintainIndexJob.join(); + } catch (InterruptedException e) { + // ignore + } + try { + maintainIndex(new NullProgressMonitor()); + } catch (CoreException e) { + MultiStatus logStatus = new MultiStatus(TasksIndexCore.ID_PLUGIN, 0, + "Failed to update task list index", e); //$NON-NLS-1$ + logStatus.add(e.getStatus()); + StatusHandler.log(logStatus); + } + } else { + maintainIndexJob.schedule(delay); + } + } + + /** + * Indicates if the given task matches the given pattern string. Uses the backing index to detect a match by looking + * for tasks that match the given pattern string. The results of the search are cached such that future calls to + * this method using the same pattern string do not require use of the backing index, making this method very + * efficient for multiple calls with the same pattern string. Cached results for a given pattern string are + * discarded if this method is called with a different pattern string. + * + * @param task + * the task to match + * @param patternString + * the pattern used to detect a match + */ + public boolean matches(ITask task, String patternString) { + if (patternString.equals(COMMAND_RESET_INDEX)) { + reindex(); + } + Lock readLock = indexReaderLock.readLock(); + readLock.lock(); + try { + + IndexReader indexReader = getIndexReader(); + if (indexReader != null) { + Set<String> hits; + + final boolean needIndexHit; + synchronized (this) { + needIndexHit = lastResults == null + || (lastPatternString == null || !lastPatternString.equals(patternString)); + } + if (needIndexHit) { + this.lastPatternString = patternString; + + hits = new HashSet<String>(); + + IndexSearcher indexSearcher = new IndexSearcher(indexReader); + try { + Query query = computeQuery(patternString); + TopDocs results = indexSearcher.search(query, maxMatchSearchHits); + for (ScoreDoc scoreDoc : results.scoreDocs) { + Document document = indexReader.document(scoreDoc.doc); + hits.add(document.get(IndexField.IDENTIFIER.fieldName())); + } + } catch (IOException e) { + StatusHandler.log(new Status(IStatus.ERROR, TasksIndexCore.ID_PLUGIN, + "Unexpected failure within task list index", e)); //$NON-NLS-1$ + } finally { + try { + indexSearcher.close(); + } catch (IOException e) { + // ignore + } + } + + } else { + hits = lastResults; + } + synchronized (this) { + if (this.indexReader == indexReader) { + this.lastPatternString = patternString; + this.lastResults = hits; + } + } + String taskIdentifier = task.getHandleIdentifier(); + return hits != null && hits.contains(taskIdentifier); + } + + } finally { + readLock.unlock(); + } + return false; + } + + public void reindex() { + rebuildIndex = true; + scheduleIndexMaintenance(MaintainIndexType.REINDEX); + } + + /** + * call to wait until index maintenance has completed + * + * @throws InterruptedException + */ + public void waitUntilIdle() throws InterruptedException { + if (!Platform.isRunning() && reindexDelay != 0L) { + // job join() behaviour is not the same when platform is not running + Logger.getLogger(TaskListIndex.class.getName()).warning( + "Index job joining may not work properly when Eclipse platform is not running"); //$NON-NLS-1$ + } + maintainIndexJob.join(); + } + + /** + * finds tasks that match the given pattern string + * + * @param patternString + * the pattern string, used to match tasks + * @param collector + * the collector that receives tasks + * @param resultsLimit + * the maximum number of tasks to find. Specifying a limit enables the index to be more efficient since + * it can skip over matching tasks that do not score highly enough. Specify {@link Integer#MAX_VALUE} if + * there should be no limit. + */ + public void find(String patternString, TaskCollector collector, int resultsLimit) { + Assert.isNotNull(patternString); + Assert.isNotNull(collector); + Assert.isTrue(resultsLimit > 0); + + Lock readLock = indexReaderLock.readLock(); + readLock.lock(); + try { + IndexReader indexReader = getIndexReader(); + if (indexReader != null) { + IndexSearcher indexSearcher = new IndexSearcher(indexReader); + try { + Query query = computeQuery(patternString); + TopDocs results = indexSearcher.search(query, resultsLimit); + for (ScoreDoc scoreDoc : results.scoreDocs) { + Document document = indexReader.document(scoreDoc.doc); + String taskIdentifier = document.get(IndexField.IDENTIFIER.fieldName()); + AbstractTask task = taskList.getTask(taskIdentifier); + if (task != null) { + collector.collect(task); + } + } + } catch (IOException e) { + StatusHandler.log(new Status(IStatus.ERROR, TasksIndexCore.ID_PLUGIN, + "Unexpected failure within task list index", e)); //$NON-NLS-1$ + } finally { + try { + indexSearcher.close(); + } catch (IOException e) { + // ignore + } + } + } + } finally { + readLock.unlock(); + } + } + + private Query computeQuery(String patternString) { + String upperPatternString = patternString.toUpperCase(); + + boolean hasBooleanSpecifiers = upperPatternString.contains(" OR ") || upperPatternString.contains(" AND ") //$NON-NLS-1$ //$NON-NLS-2$ + || upperPatternString.contains(" NOT "); //$NON-NLS-1$ + + if (patternString.indexOf(':') == -1 && !hasBooleanSpecifiers && defaultField == IndexField.SUMMARY + && patternString.indexOf('"') == -1) { + return new PrefixQuery(new Term(defaultField.fieldName(), patternString)); + } + QueryParser qp = new QueryParser(Version.LUCENE_CURRENT, defaultField.fieldName(), new TaskAnalyzer()); + Query q; + try { + q = qp.parse(patternString); + } catch (ParseException e) { + return new PrefixQuery(new Term(defaultField.fieldName(), patternString)); + } + + // relax term clauses to be prefix clauses so that we get results close + // to what we're expecting + // from previous task list search + if (q instanceof BooleanQuery) { + BooleanQuery query = (BooleanQuery) q; + for (BooleanClause clause : query.getClauses()) { + if (clause.getQuery() instanceof TermQuery) { + TermQuery termQuery = (TermQuery) clause.getQuery(); + clause.setQuery(new PrefixQuery(termQuery.getTerm())); + } + if (!hasBooleanSpecifiers) { + clause.setOccur(Occur.MUST); + } + } + } else if (q instanceof TermQuery) { + return new PrefixQuery(((TermQuery) q).getTerm()); + } + return q; + } + + public void close() { + dataManager.removeListener(this); + taskList.removeChangeListener(this); + repositoryManager.removeListener(this); + + maintainIndexJob.cancel(); + try { + maintainIndexJob.join(); + } catch (InterruptedException e) { + // ignore + } + + Lock writeLock = indexReaderLock.writeLock(); + writeLock.lock(); + try { + synchronized (this) { + if (indexReader != null) { + try { + indexReader.close(); + } catch (IOException e) { + // ignore + } + indexReader = null; + } + } + try { + directory.close(); + } catch (IOException e) { + StatusHandler.log(new Status(IStatus.ERROR, TasksIndexCore.ID_PLUGIN, + "Cannot close index: " + e.getMessage(), e)); //$NON-NLS-1$ + } + } finally { + writeLock.unlock(); + } + } + + private IndexReader getIndexReader() { + try { + synchronized (this) { + if (indexReader == null) { + indexReader = IndexReader.open(directory, true); + lastResults = null; + } + return indexReader; + } + } catch (CorruptIndexException e) { + rebuildIndex = true; + if (maintainIndexJob != null) { + scheduleIndexMaintenance(MaintainIndexType.REINDEX); + } + } catch (FileNotFoundException e) { + rebuildIndex = true; + // expected if the index doesn't exist + } catch (IOException e) { + // ignore + } + return null; + } + + public void taskDataUpdated(TaskDataManagerEvent event) { + reindex(event.getTask(), event.getTaskData()); + } + + public void editsDiscarded(TaskDataManagerEvent event) { + reindex(event.getTask(), event.getTaskData()); + } + + public void containersChanged(Set<TaskContainerDelta> containers) { + for (TaskContainerDelta delta : containers) { + switch (delta.getKind()) { + case ADDED: + case REMOVED: + case CONTENT: + IRepositoryElement element = delta.getElement(); + if (element instanceof ITask) { + ITask task = (ITask) element; + if ("local".equals(((AbstractTask) task).getConnectorKind())) { //$NON-NLS-1$ + reindex(task, null); + } + } + } + } + } + + /** + * advanced usage: cause the given task to be reindexed using {@link MaintainIndexType#REINDEX reindex scheduling + * rule}. + * + * @param task + * the task + * @param taskData + * the task data, or nul if it's not available + */ + protected void reindex(ITask task, TaskData taskData) { + if (task == null) { + // this can happen when edits are discarded + return; + } + if (!taskIsIndexable(task, taskData)) { + return; + } + synchronized (reindexQueue) { + reindexQueue.put(task, taskData); + } + scheduleIndexMaintenance(MaintainIndexType.REINDEX); + } + + private void addIndexedAttributes(Document document, ITask task, TaskAttribute root) { + addIndexedAttribute(document, IndexField.TASK_KEY, task.getTaskKey()); + addIndexedAttribute(document, IndexField.REPOSITORY_URL, task.getRepositoryUrl()); + addIndexedAttribute(document, IndexField.SUMMARY, task.getSummary()); + + for (TaskAttribute contentAttribute : computeContentAttributes(root)) { + addIndexedAttribute(document, IndexField.CONTENT, contentAttribute); + } + + addIndexedDateAttributes(document, task); + + List<TaskAttribute> commentAttributes = root.getTaskData() + .getAttributeMapper() + .getAttributesByType(root.getTaskData(), TaskAttribute.TYPE_COMMENT); + for (TaskAttribute commentAttribute : commentAttributes) { + + TaskComment taskComment = new TaskComment(root.getTaskData().getAttributeMapper().getTaskRepository(), + task, commentAttribute); + root.getTaskData().getAttributeMapper().updateTaskComment(taskComment, commentAttribute); + + String text = taskComment.getText(); + if (text.length() != 0) { + addIndexedAttribute(document, IndexField.CONTENT, text); + } + IRepositoryPerson author = taskComment.getAuthor(); + if (author != null) { + addIndexedAttribute(document, IndexField.PERSON, author.getPersonId()); + } + } + + List<TaskAttribute> personAttributes = root.getTaskData() + .getAttributeMapper() + .getAttributesByType(root.getTaskData(), TaskAttribute.TYPE_PERSON); + for (TaskAttribute personAttribute : personAttributes) { + addIndexedAttribute(document, IndexField.PERSON, personAttribute); + } + + for (IndexField field : IndexField.values()) { + if (field.getAttributeId() != null) { + addIndexedAttribute(document, field, root.getMappedAttribute(field.getAttributeId())); + } + } + } + + /** + * compute attributes that should be indexed as {@link IndexField#CONTENT} + */ + private Collection<TaskAttribute> computeContentAttributes(TaskAttribute root) { + Set<TaskAttribute> attributes = new LinkedHashSet<TaskAttribute>(); + + // add default content attributes + { + TaskAttribute attribute = root.getMappedAttribute(TaskAttribute.SUMMARY); + if (attribute != null) { + attributes.add(attribute); + } + attribute = root.getMappedAttribute(TaskAttribute.DESCRIPTION); + if (attribute != null) { + attributes.add(attribute); + } + } + + for (TaskAttribute attribute : root.getAttributes().values()) { + if (Boolean.parseBoolean(attribute.getMetaData().getValue(META_INDEXED_AS_CONTENT))) { + attributes.add(attribute); + } + } + + return attributes; + } + + private void addIndexedAttributes(Document document, ITask task) { + addIndexedAttribute(document, IndexField.TASK_KEY, task.getTaskKey()); + addIndexedAttribute(document, IndexField.REPOSITORY_URL, task.getRepositoryUrl()); + addIndexedAttribute(document, IndexField.SUMMARY, task.getSummary()); + addIndexedAttribute(document, IndexField.CONTENT, task.getSummary()); + addIndexedAttribute(document, IndexField.CONTENT, ((AbstractTask) task).getNotes()); + addIndexedDateAttributes(document, task); + } + + private void addIndexedDateAttributes(Document document, ITask task) { + addIndexedAttribute(document, IndexField.COMPLETION_DATE, task.getCompletionDate()); + addIndexedAttribute(document, IndexField.CREATION_DATE, task.getCreationDate()); + addIndexedAttribute(document, IndexField.DUE_DATE, task.getDueDate()); + addIndexedAttribute(document, IndexField.MODIFICATION_DATE, task.getModificationDate()); + } + + private void addIndexedAttribute(Document document, IndexField indexField, TaskAttribute attribute) { + if (attribute == null) { + return; + } + List<String> values = attribute.getTaskData().getAttributeMapper().getValueLabels(attribute); + if (values.isEmpty()) { + return; + } + + if (indexField.isPersonField()) { + IRepositoryPerson repositoryPerson = attribute.getTaskData() + .getAttributeMapper() + .getRepositoryPerson(attribute); + addIndexedAttribute(document, indexField, repositoryPerson); + + if (values.size() <= 1) { + return; + } + } + + for (String value : values) { + if (value.length() != 0) { + addIndexedAttribute(document, indexField, value); + } + } + } + + private void addIndexedAttribute(Document document, IndexField indexField, IRepositoryPerson person) { + if (person != null) { + addIndexedAttribute(document, indexField, person.getPersonId()); + addIndexedAttribute(document, indexField, person.getName()); + } + } + + private void addIndexedAttribute(Document document, IndexField indexField, String value) { + if (value == null) { + return; + } + Field field = document.getField(indexField.fieldName()); + if (field == null) { + field = new Field(indexField.fieldName(), value, Store.YES, org.apache.lucene.document.Field.Index.ANALYZED); + document.add(field); + } else { + String existingValue = field.stringValue(); + if (indexField != IndexField.PERSON || !existingValue.contains(value)) { + field.setValue(existingValue + " " + value); //$NON-NLS-1$ + } + } + } + + private void addIndexedAttribute(Document document, IndexField indexField, Date date) { + if (date == null) { + return; + } + // FIXME: date tools converts dates to GMT, and we don't really want that. So + // move the date by the GMT offset if there is any + + String value = DateTools.dateToString(date, Resolution.HOUR); + Field field = document.getField(indexField.fieldName()); + if (field == null) { + field = new Field(indexField.fieldName(), value, Store.YES, org.apache.lucene.document.Field.Index.ANALYZED); + document.add(field); + } else { + field.setValue(value); + } + } + + /** + * Computes a query element for a field that must lie in a specified date range. + * + * @param field + * the field + * @param lowerBoundInclusive + * the date lower bound that the field value must match, inclusive + * @param upperBoundInclusive + * the date upper bound that the field value must match, inclusive + * @return + */ + public String computeQueryFieldDateRange(IndexField field, Date lowerBoundInclusive, Date upperBoundInclusive) { + return field.fieldName() + + ":[" + DateTools.dateToString(lowerBoundInclusive, Resolution.DAY) + " TO " + DateTools.dateToString(upperBoundInclusive, Resolution.DAY) + "]"; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ + } + + /** + * Indicates if the given task is indexable. The default implementation returns true, subclasses may override to + * filter some tasks from the task list. This method may be called more than once per task, with some calls omitting + * the task data. In this way implementations can avoid loading task data if the decision to filter tasks can be + * based on the ITask alone. Implementations that must read the task data in order to determine eligibility for + * indexing should return true for tasks where the provided task data is null. + * + * @param task + * the task + * @param taskData + * the task data, or null if there is no task data + * @return true if the given task should be indexed, otherwise false. + */ + protected boolean taskIsIndexable(ITask task, TaskData taskData) { + return true; + } + + /** + * Escapes special characters in the given literal value so that they are not interpreted as special characters in a + * query. + * + * @param value + * the value to escape + * @return a representation of the value with characters escaped + */ + public String escapeFieldValue(String value) { + // see http://lucene.apache.org/java/2_9_1/queryparsersyntax.html#Escaping%20Special%20Characters + String escaped = value.replaceAll("([\\+\\-\\!\\(\\)\\{\\}\\[\\]^\"~\\*\\?:\\\\]|&&|\\|\\|)", "\\\\$1"); //$NON-NLS-1$ //$NON-NLS-2$ + return escaped; + } + + private void maintainIndex(IProgressMonitor m) throws CoreException { + final int WORK_PER_SEGMENT = 1000; + SubMonitor monitor = SubMonitor.convert(m, 2 * WORK_PER_SEGMENT); + try { + try { + if (!rebuildIndex) { + try { + IndexReader reader = IndexReader.open(directory, false); + reader.close(); + } catch (CorruptIndexException e) { + rebuildIndex = true; + } + } + + if (rebuildIndex) { + synchronized (reindexQueue) { + reindexQueue.clear(); + } + + IStatus status = rebuildIndexCompletely(monitor.newChild(WORK_PER_SEGMENT)); + if (!status.isOK()) { + StatusHandler.log(status); + } + } else { + monitor.worked(WORK_PER_SEGMENT); + } + + // index any tasks that have been changed + indexQueuedTasks(monitor.newChild(WORK_PER_SEGMENT)); + + // prevent new searches from reading the now-stale index + closeIndexReader(); + } catch (IOException e) { + throw new CoreException(new Status(IStatus.ERROR, TasksIndexCore.ID_PLUGIN, + "Unexpected exception: " + e.getMessage(), e)); //$NON-NLS-1$ + } + } finally { + monitor.done(); + } + } + + private void closeIndexReader() throws IOException { + Lock writeLock = indexReaderLock.writeLock(); + writeLock.lock(); + try { + synchronized (this) { + if (indexReader != null) { + indexReader.close(); + indexReader = null; + } + } + } finally { + writeLock.unlock(); + } + } + + private void indexQueuedTasks(SubMonitor monitor) throws CorruptIndexException, LockObtainFailedException, + IOException { + + synchronized (reindexQueue) { + if (reindexQueue.isEmpty()) { + return; + } + + monitor.beginTask(Messages.TaskListIndex_task_rebuilding_index, reindexQueue.size()); + } + + try { + IndexWriter writer = null; + try { + Map<ITask, TaskData> workingQueue = new HashMap<ITask, TaskData>(); + + // reindex tasks that are in the reindexQueue, making multiple passes so that we catch anything + // added/changed while we were reindexing + for (;;) { + workingQueue.clear(); + + synchronized (reindexQueue) { + if (reindexQueue.isEmpty()) { + break; + } + // move items from the reindexQueue to the temporary working queue + workingQueue.putAll(reindexQueue); + reindexQueue.keySet().removeAll(workingQueue.keySet()); + } + + if (writer == null) { + writer = new IndexWriter(directory, new TaskAnalyzer(), false, + IndexWriter.MaxFieldLength.UNLIMITED); + } + + monitor.setWorkRemaining(workingQueue.size()); + + for (Entry<ITask, TaskData> entry : workingQueue.entrySet()) { + ITask task = entry.getKey(); + TaskData taskData = entry.getValue(); + + writer.deleteDocuments(new Term(IndexField.IDENTIFIER.fieldName(), task.getHandleIdentifier())); + + add(writer, task, taskData); + + monitor.worked(1); + } + } + } finally { + if (writer != null) { + writer.close(); + } + } + } finally { + monitor.done(); + } + } + + private class TaskListState implements ITaskListRunnable { + List<ITask> indexableTasks; + + public void execute(IProgressMonitor monitor) throws CoreException { + Collection<AbstractTask> tasks = taskList.getAllTasks(); + indexableTasks = new ArrayList<ITask>(tasks.size()); + + for (ITask task : tasks) { + if (taskIsIndexable(task, null)) { + indexableTasks.add(task); + } + } + } + + } + + private IStatus rebuildIndexCompletely(SubMonitor monitor) throws CorruptIndexException, LockObtainFailedException, + IOException, CoreException { + + MultiStatus multiStatus = new MultiStatus(TasksIndexCore.ID_PLUGIN, 0, null, null); + + // get indexable tasks from the task list + final TaskListState taskListState = new TaskListState(); + taskList.run(taskListState, monitor.newChild(0)); + + monitor.beginTask(Messages.TaskListIndex_task_rebuilding_index, taskListState.indexableTasks.size()); + try { + final IndexWriter writer = new IndexWriter(directory, new TaskAnalyzer(), true, + IndexWriter.MaxFieldLength.UNLIMITED); + try { + + for (ITask task : taskListState.indexableTasks) { + if (taskIsIndexable(task, null)) { + try { + TaskData taskData = dataManager.getTaskData(task); + add(writer, task, taskData); + } catch (CoreException e) { + // an individual task data error should not prevent the index from updating + multiStatus.add(e.getStatus()); + } + } + monitor.worked(1); + } + synchronized (this) { + rebuildIndex = false; + } + } finally { + writer.close(); + } + } finally { + monitor.done(); + } + return multiStatus; + } + + /** + * @param writer + * @param task + * the task + * @param taskData + * may be null for local tasks + * @throws CorruptIndexException + * @throws IOException + */ + private void add(IndexWriter writer, ITask task, TaskData taskData) throws CorruptIndexException, IOException { + if (!taskIsIndexable(task, taskData)) { + return; + } + + Document document = new Document(); + + document.add(new Field(IndexField.IDENTIFIER.fieldName(), task.getHandleIdentifier(), Store.YES, + org.apache.lucene.document.Field.Index.ANALYZED)); + if (taskData == null) { + if ("local".equals(((AbstractTask) task).getConnectorKind())) { //$NON-NLS-1$ + addIndexedAttributes(document, task); + } else { + return; + } + } else { + addIndexedAttributes(document, task, taskData.getRoot()); + } + writer.addDocument(document); + } + + public void repositoryAdded(TaskRepository repository) { + // ignore + } + + public void repositoryRemoved(TaskRepository repository) { + // ignore + } + + public void repositorySettingsChanged(TaskRepository repository) { + // ignore + } + + public void repositoryUrlChanged(TaskRepository repository, String oldUrl) { + reindex(); + } +} diff --git a/org.eclipse.mylyn.tasks.index.tests/src/org/eclipse/mylyn/internal/tasks/index/tests/TaskListIndexTest.java b/org.eclipse.mylyn.tasks.index.tests/src/org/eclipse/mylyn/internal/tasks/index/tests/TaskListIndexTest.java index 4de695562..724449249 100644 --- a/org.eclipse.mylyn.tasks.index.tests/src/org/eclipse/mylyn/internal/tasks/index/tests/TaskListIndexTest.java +++ b/org.eclipse.mylyn.tasks.index.tests/src/org/eclipse/mylyn/internal/tasks/index/tests/TaskListIndexTest.java @@ -1,409 +1,409 @@ -/*******************************************************************************
- * Copyright (c) 2011, 2012 Tasktop Technologies and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Tasktop Technologies - initial API and implementation
- *******************************************************************************/
-
-package org.eclipse.mylyn.internal.tasks.index.tests;
-
-import static junit.framework.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.logging.Logger;
-
-import junit.framework.Assert;
-
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.mylyn.commons.core.DelegatingProgressMonitor;
-import org.eclipse.mylyn.internal.tasks.core.AbstractTask;
-import org.eclipse.mylyn.internal.tasks.core.LocalTask;
-import org.eclipse.mylyn.internal.tasks.index.core.TaskListIndex;
-import org.eclipse.mylyn.internal.tasks.index.core.TaskListIndex.IndexField;
-import org.eclipse.mylyn.internal.tasks.index.core.TaskListIndex.TaskCollector;
-import org.eclipse.mylyn.internal.tasks.index.tests.util.MockTestContext;
-import org.eclipse.mylyn.tasks.core.IRepositoryManager;
-import org.eclipse.mylyn.tasks.core.ITask;
-import org.eclipse.mylyn.tasks.core.data.TaskAttribute;
-import org.eclipse.mylyn.tasks.core.data.TaskData;
-import org.eclipse.mylyn.tasks.core.data.TaskMapper;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * @author David Green
- */
-public class TaskListIndexTest {
-
- private static class TestTaskCollector extends TaskCollector {
-
- private final List<ITask> tasks = new ArrayList<ITask>();
-
- @Override
- public void collect(ITask task) {
- tasks.add(task);
- }
-
- public List<ITask> getTasks() {
- return tasks;
- }
- }
-
- private MockTestContext context;
-
- private TaskListIndex index;
-
- private File tempDir;
-
- @Before
- public void setup() throws IOException {
- tempDir = File.createTempFile(TaskListIndexTest.class.getSimpleName(), ".tmp");
- tempDir.delete();
- tempDir.mkdirs();
-
- assertTrue(tempDir.exists() && tempDir.isDirectory());
-
- context = new MockTestContext();
- }
-
- @After
- public void tearDown() {
- if (index != null) {
- try {
- index.waitUntilIdle();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- index.close();
- index = null;
- }
- if (tempDir != null) {
- delete(tempDir);
- assertFalse(tempDir.exists());
- }
- }
-
- private void delete(File file) {
- if (file.isDirectory()) {
- File[] children = file.listFiles();
- if (children != null) {
- for (File child : children) {
- delete(child);
- }
- }
- }
- if (!file.delete()) {
- Logger.getLogger(TaskListIndexTest.class.getName()).severe("Cannot delete: " + file);
- }
- }
-
- private void setupIndex() {
- index = new TaskListIndex(context.getTaskList(), context.getDataManager(),
- (IRepositoryManager) context.getRepositoryManager(), tempDir, 0L);
- index.setDefaultField(IndexField.CONTENT);
- index.setReindexDelay(0L);
- }
-
- @Test
- public void testMatchesLocalTaskOnSummary() throws InterruptedException {
- setupIndex();
-
- ITask task = context.createLocalTask();
-
- index.waitUntilIdle();
-
- index.setDefaultField(IndexField.CONTENT);
-
- assertTrue(index.matches(task, task.getSummary()));
- assertFalse(index.matches(task, "" + System.currentTimeMillis()));
-
- index.setDefaultField(IndexField.SUMMARY);
-
- assertTrue(index.matches(task, task.getSummary()));
- assertFalse(index.matches(task, "" + System.currentTimeMillis()));
- }
-
- @Test
- public void testMatchesLocalTaskOnDescription() throws InterruptedException {
- setupIndex();
-
- ITask task = context.createLocalTask();
-
- index.waitUntilIdle();
-
- index.setDefaultField(IndexField.CONTENT);
-
- assertTrue(index.matches(task, ((LocalTask) task).getNotes()));
- assertFalse(index.matches(task, "unlikely-akjfsaow"));
-
- index.setDefaultField(IndexField.SUMMARY);
-
- assertFalse(index.matches(task, ((LocalTask) task).getNotes()));
- }
-
- @Test
- public void testMatchesRepositoryTaskOnSummary() throws InterruptedException, CoreException {
- setupIndex();
-
- ITask task = context.createRepositoryTask();
-
- index.waitUntilIdle();
-
- index.setDefaultField(IndexField.CONTENT);
-
- assertTrue(index.matches(task, task.getSummary()));
- assertFalse(index.matches(task, "" + System.currentTimeMillis()));
-
- index.setDefaultField(IndexField.SUMMARY);
-
- assertTrue(index.matches(task, task.getSummary()));
- assertFalse(index.matches(task, "" + System.currentTimeMillis()));
- }
-
- @Test
- public void testMatchesRepositoryTaskOnDescription() throws InterruptedException, CoreException {
- setupIndex();
-
- ITask task = context.createRepositoryTask();
-
- index.waitUntilIdle();
-
- index.setDefaultField(IndexField.CONTENT);
-
- TaskData taskData = context.getDataManager().getTaskData(task);
- assertNotNull(taskData);
-
- TaskMapper taskMapping = context.getMockRepositoryConnector().getTaskMapping(taskData);
-
- assertTrue(index.matches(task, taskMapping.getDescription()));
- assertFalse(index.matches(task, "unlikely-akjfsaow"));
-
- index.setDefaultField(IndexField.SUMMARY);
-
- assertFalse(index.matches(task, taskMapping.getDescription()));
- }
-
- @Test
- public void testFind() throws InterruptedException {
- setupIndex();
-
- ITask task = context.createLocalTask();
-
- index.waitUntilIdle();
-
- index.setDefaultField(IndexField.SUMMARY);
-
- TestTaskCollector collector = new TestTaskCollector();
- index.find(task.getSummary(), collector, 1000);
-
- assertEquals(1, collector.getTasks().size());
- assertTrue(collector.getTasks().contains(task));
- }
-
- @Test
- public void testMatchesRepositoryTaskOnCreationDate() throws InterruptedException, CoreException {
- setupIndex();
-
- ITask task = context.createRepositoryTask();
-
- Date creationDate = task.getCreationDate();
- assertNotNull(creationDate);
-
- index.waitUntilIdle();
-
- assertFalse(index.matches(task, IndexField.CREATION_DATE.fieldName() + ":[20010101 TO 20010105]"));
-
- String matchDate = new SimpleDateFormat("yyyyMMdd").format(creationDate);
- matchDate = Integer.toString(Integer.parseInt(matchDate) + 2);
-
- String patternString = IndexField.CREATION_DATE.fieldName() + ":[20111019 TO " + matchDate + "]";
-
- System.out.println(patternString);
-
- assertTrue(index.matches(task, patternString));
- }
-
- @Test
- public void testMatchesOnRepositoryUrl() throws Exception {
- setupIndex();
-
- ITask repositoryTask = context.createRepositoryTask();
- ITask localTask = context.createLocalTask();
-
- index.waitUntilIdle();
-
- index.setDefaultField(IndexField.CONTENT);
-
- TaskData taskData = context.getDataManager().getTaskData(repositoryTask);
-
- // sanity
- assertNotNull(taskData);
- assertNotNull(taskData.getRepositoryUrl());
- assertFalse(taskData.getRepositoryUrl().length() == 0);
-
- // setup descriptions so that they will both match
- final String content = "RepositoryUrl";
- taskData.getRoot().getMappedAttribute(TaskAttribute.DESCRIPTION).setValue(content);
- ((AbstractTask) localTask).setNotes(content);
-
- context.getDataManager().putSubmittedTaskData(repositoryTask, taskData, new DelegatingProgressMonitor());
-
- Set<ITask> changedElements = new HashSet<ITask>();
- changedElements.add(localTask);
- changedElements.add(repositoryTask);
- context.getTaskList().notifyElementsChanged(changedElements);
-
- index.waitUntilIdle();
-
- assertTrue(index.matches(localTask, content));
- assertTrue(index.matches(repositoryTask, content));
-
- String repositoryUrlQuery = content + " AND " + IndexField.REPOSITORY_URL.fieldName() + ":\""
- + index.escapeFieldValue(repositoryTask.getRepositoryUrl()) + "\"";
- assertFalse(index.matches(localTask, repositoryUrlQuery));
- assertTrue(index.matches(repositoryTask, repositoryUrlQuery));
- }
-
- @Test
- public void testCharacterEscaping() {
- setupIndex();
- for (String special : new String[] { "+", "-", "&&", "||", "!", "(", ")", "{", "}", "[", "]", "^", "\"", "~",
- "*", "?", ":", "\\" }) {
- assertEquals("a\\" + special + "b", index.escapeFieldValue("a" + special + "b"));
- }
- }
-
- @Test
- public void testAttributeMetadataAffectsIndexing() throws CoreException, InterruptedException {
- setupIndex();
-
- ITask repositoryTask = context.createRepositoryTask();
-
- index.waitUntilIdle();
- index.setDefaultField(IndexField.CONTENT);
-
- TaskData taskData = context.getDataManager().getTaskData(repositoryTask);
-
- // sanity
- assertNotNull(taskData);
-
- final String content = "c" + System.currentTimeMillis();
-
- // setup data so that it will match
- TaskAttribute attribute = taskData.getRoot().createAttribute("unusualIndexedAttribute");
- attribute.setValue(content);
-
- // update
- context.getDataManager().putSubmittedTaskData(repositoryTask, taskData, new DelegatingProgressMonitor());
-
- // verify index doesn't match search term
- assertFalse(index.matches(repositoryTask, content));
-
- // now make data indexable
- attribute.getMetaData().putValue(TaskAttribute.META_INDEXED_AS_CONTENT, "true");
- // update
- context.getDataManager().putSubmittedTaskData(repositoryTask, taskData, new DelegatingProgressMonitor());
-
- // should now match
- assertTrue(index.matches(repositoryTask, content));
- }
-
- /**
- * Verify that multiple threads can concurrently use the index to find tasks, i.e. that no threads are blocked from
- * finding tasks by other threads.
- */
- @Test
- public void testMultithreadedAccessOnFind() throws CoreException, InterruptedException, ExecutionException {
- setupIndex();
-
- final ITask repositoryTask = context.createRepositoryTask();
-
- index.waitUntilIdle();
- index.setDefaultField(IndexField.CONTENT);
-
- final int nThreads = 10;
- final int[] concurrencyLevel = new int[1];
- ExecutorService executorService = Executors.newFixedThreadPool(nThreads);
- try {
- Collection<Callable<Object>> tasks = new HashSet<Callable<Object>>();
- for (int x = 0; x < nThreads; ++x) {
- tasks.add(new Callable<Object>() {
-
- public Object call() throws Exception {
- final int[] hitCount = new int[1];
- index.find(repositoryTask.getSummary(), new TaskCollector() {
-
- @Override
- public void collect(ITask task) {
- synchronized (concurrencyLevel) {
- ++concurrencyLevel[0];
- if (concurrencyLevel[0] < nThreads) {
- try {
- concurrencyLevel.wait(5000L);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- } else {
- concurrencyLevel.notifyAll();
- }
- }
- ++hitCount[0];
- }
- }, 100);
- return hitCount[0] == 1;
- }
- });
- }
- List<Future<Object>> futures = executorService.invokeAll(tasks);
- for (Future<Object> future : futures) {
- assertEquals(Boolean.TRUE, future.get());
- }
- Assert.assertEquals(nThreads, concurrencyLevel[0]);
- } finally {
- executorService.shutdownNow();
- }
- }
-
- @Test
- public void testRepositoryUrlChanged() throws InterruptedException, CoreException {
- setupIndex();
-
- ITask repositoryTask = context.createRepositoryTask();
- final String originalHandle = repositoryTask.getHandleIdentifier();
-
- index.waitUntilIdle();
-
- final String newUrl = context.getMockRepository().getRepositoryUrl() + "/changed";
-
- context.refactorMockRepositoryUrl(newUrl);
-
- Assert.assertFalse(originalHandle.equals(repositoryTask.getHandleIdentifier()));
-
- index.waitUntilIdle();
-
- Assert.assertTrue(index.matches(repositoryTask,
- IndexField.IDENTIFIER.fieldName() + ":" + index.escapeFieldValue(repositoryTask.getHandleIdentifier())));
- }
-
-}
+/******************************************************************************* + * Copyright (c) 2011, 2012 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.index.tests; + +import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.logging.Logger; + +import junit.framework.Assert; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.mylyn.commons.core.DelegatingProgressMonitor; +import org.eclipse.mylyn.internal.tasks.core.AbstractTask; +import org.eclipse.mylyn.internal.tasks.core.LocalTask; +import org.eclipse.mylyn.internal.tasks.index.core.TaskListIndex; +import org.eclipse.mylyn.internal.tasks.index.core.TaskListIndex.IndexField; +import org.eclipse.mylyn.internal.tasks.index.core.TaskListIndex.TaskCollector; +import org.eclipse.mylyn.internal.tasks.index.tests.util.MockTestContext; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tasks.core.data.TaskMapper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * @author David Green + */ +public class TaskListIndexTest { + + private static class TestTaskCollector extends TaskCollector { + + private final List<ITask> tasks = new ArrayList<ITask>(); + + @Override + public void collect(ITask task) { + tasks.add(task); + } + + public List<ITask> getTasks() { + return tasks; + } + } + + private MockTestContext context; + + private TaskListIndex index; + + private File tempDir; + + @Before + public void setup() throws IOException { + tempDir = File.createTempFile(TaskListIndexTest.class.getSimpleName(), ".tmp"); + tempDir.delete(); + tempDir.mkdirs(); + + assertTrue(tempDir.exists() && tempDir.isDirectory()); + + context = new MockTestContext(); + } + + @After + public void tearDown() { + if (index != null) { + try { + index.waitUntilIdle(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + index.close(); + index = null; + } + if (tempDir != null) { + delete(tempDir); + assertFalse(tempDir.exists()); + } + } + + private void delete(File file) { + if (file.isDirectory()) { + File[] children = file.listFiles(); + if (children != null) { + for (File child : children) { + delete(child); + } + } + } + if (!file.delete()) { + Logger.getLogger(TaskListIndexTest.class.getName()).severe("Cannot delete: " + file); + } + } + + private void setupIndex() { + index = new TaskListIndex(context.getTaskList(), context.getDataManager(), + (IRepositoryManager) context.getRepositoryManager(), tempDir, 0L); + index.setDefaultField(IndexField.CONTENT); + index.setReindexDelay(0L); + } + + @Test + public void testMatchesLocalTaskOnSummary() throws InterruptedException { + setupIndex(); + + ITask task = context.createLocalTask(); + + index.waitUntilIdle(); + + index.setDefaultField(IndexField.CONTENT); + + assertTrue(index.matches(task, task.getSummary())); + assertFalse(index.matches(task, "" + System.currentTimeMillis())); + + index.setDefaultField(IndexField.SUMMARY); + + assertTrue(index.matches(task, task.getSummary())); + assertFalse(index.matches(task, "" + System.currentTimeMillis())); + } + + @Test + public void testMatchesLocalTaskOnDescription() throws InterruptedException { + setupIndex(); + + ITask task = context.createLocalTask(); + + index.waitUntilIdle(); + + index.setDefaultField(IndexField.CONTENT); + + assertTrue(index.matches(task, ((LocalTask) task).getNotes())); + assertFalse(index.matches(task, "unlikely-akjfsaow")); + + index.setDefaultField(IndexField.SUMMARY); + + assertFalse(index.matches(task, ((LocalTask) task).getNotes())); + } + + @Test + public void testMatchesRepositoryTaskOnSummary() throws InterruptedException, CoreException { + setupIndex(); + + ITask task = context.createRepositoryTask(); + + index.waitUntilIdle(); + + index.setDefaultField(IndexField.CONTENT); + + assertTrue(index.matches(task, task.getSummary())); + assertFalse(index.matches(task, "" + System.currentTimeMillis())); + + index.setDefaultField(IndexField.SUMMARY); + + assertTrue(index.matches(task, task.getSummary())); + assertFalse(index.matches(task, "" + System.currentTimeMillis())); + } + + @Test + public void testMatchesRepositoryTaskOnDescription() throws InterruptedException, CoreException { + setupIndex(); + + ITask task = context.createRepositoryTask(); + + index.waitUntilIdle(); + + index.setDefaultField(IndexField.CONTENT); + + TaskData taskData = context.getDataManager().getTaskData(task); + assertNotNull(taskData); + + TaskMapper taskMapping = context.getMockRepositoryConnector().getTaskMapping(taskData); + + assertTrue(index.matches(task, taskMapping.getDescription())); + assertFalse(index.matches(task, "unlikely-akjfsaow")); + + index.setDefaultField(IndexField.SUMMARY); + + assertFalse(index.matches(task, taskMapping.getDescription())); + } + + @Test + public void testFind() throws InterruptedException { + setupIndex(); + + ITask task = context.createLocalTask(); + + index.waitUntilIdle(); + + index.setDefaultField(IndexField.SUMMARY); + + TestTaskCollector collector = new TestTaskCollector(); + index.find(task.getSummary(), collector, 1000); + + assertEquals(1, collector.getTasks().size()); + assertTrue(collector.getTasks().contains(task)); + } + + @Test + public void testMatchesRepositoryTaskOnCreationDate() throws InterruptedException, CoreException { + setupIndex(); + + ITask task = context.createRepositoryTask(); + + Date creationDate = task.getCreationDate(); + assertNotNull(creationDate); + + index.waitUntilIdle(); + + assertFalse(index.matches(task, IndexField.CREATION_DATE.fieldName() + ":[20010101 TO 20010105]")); + + String matchDate = new SimpleDateFormat("yyyyMMdd").format(creationDate); + matchDate = Integer.toString(Integer.parseInt(matchDate) + 2); + + String patternString = IndexField.CREATION_DATE.fieldName() + ":[20111019 TO " + matchDate + "]"; + + System.out.println(patternString); + + assertTrue(index.matches(task, patternString)); + } + + @Test + public void testMatchesOnRepositoryUrl() throws Exception { + setupIndex(); + + ITask repositoryTask = context.createRepositoryTask(); + ITask localTask = context.createLocalTask(); + + index.waitUntilIdle(); + + index.setDefaultField(IndexField.CONTENT); + + TaskData taskData = context.getDataManager().getTaskData(repositoryTask); + + // sanity + assertNotNull(taskData); + assertNotNull(taskData.getRepositoryUrl()); + assertFalse(taskData.getRepositoryUrl().length() == 0); + + // setup descriptions so that they will both match + final String content = "RepositoryUrl"; + taskData.getRoot().getMappedAttribute(TaskAttribute.DESCRIPTION).setValue(content); + ((AbstractTask) localTask).setNotes(content); + + context.getDataManager().putSubmittedTaskData(repositoryTask, taskData, new DelegatingProgressMonitor()); + + Set<ITask> changedElements = new HashSet<ITask>(); + changedElements.add(localTask); + changedElements.add(repositoryTask); + context.getTaskList().notifyElementsChanged(changedElements); + + index.waitUntilIdle(); + + assertTrue(index.matches(localTask, content)); + assertTrue(index.matches(repositoryTask, content)); + + String repositoryUrlQuery = content + " AND " + IndexField.REPOSITORY_URL.fieldName() + ":\"" + + index.escapeFieldValue(repositoryTask.getRepositoryUrl()) + "\""; + assertFalse(index.matches(localTask, repositoryUrlQuery)); + assertTrue(index.matches(repositoryTask, repositoryUrlQuery)); + } + + @Test + public void testCharacterEscaping() { + setupIndex(); + for (String special : new String[] { "+", "-", "&&", "||", "!", "(", ")", "{", "}", "[", "]", "^", "\"", "~", + "*", "?", ":", "\\" }) { + assertEquals("a\\" + special + "b", index.escapeFieldValue("a" + special + "b")); + } + } + + @Test + public void testAttributeMetadataAffectsIndexing() throws CoreException, InterruptedException { + setupIndex(); + + ITask repositoryTask = context.createRepositoryTask(); + + index.waitUntilIdle(); + index.setDefaultField(IndexField.CONTENT); + + TaskData taskData = context.getDataManager().getTaskData(repositoryTask); + + // sanity + assertNotNull(taskData); + + final String content = "c" + System.currentTimeMillis(); + + // setup data so that it will match + TaskAttribute attribute = taskData.getRoot().createAttribute("unusualIndexedAttribute"); + attribute.setValue(content); + + // update + context.getDataManager().putSubmittedTaskData(repositoryTask, taskData, new DelegatingProgressMonitor()); + + // verify index doesn't match search term + assertFalse(index.matches(repositoryTask, content)); + + // now make data indexable + attribute.getMetaData().putValue(TaskAttribute.META_INDEXED_AS_CONTENT, "true"); + // update + context.getDataManager().putSubmittedTaskData(repositoryTask, taskData, new DelegatingProgressMonitor()); + + // should now match + assertTrue(index.matches(repositoryTask, content)); + } + + /** + * Verify that multiple threads can concurrently use the index to find tasks, i.e. that no threads are blocked from + * finding tasks by other threads. + */ + @Test + public void testMultithreadedAccessOnFind() throws CoreException, InterruptedException, ExecutionException { + setupIndex(); + + final ITask repositoryTask = context.createRepositoryTask(); + + index.waitUntilIdle(); + index.setDefaultField(IndexField.CONTENT); + + final int nThreads = 10; + final int[] concurrencyLevel = new int[1]; + ExecutorService executorService = Executors.newFixedThreadPool(nThreads); + try { + Collection<Callable<Object>> tasks = new HashSet<Callable<Object>>(); + for (int x = 0; x < nThreads; ++x) { + tasks.add(new Callable<Object>() { + + public Object call() throws Exception { + final int[] hitCount = new int[1]; + index.find(repositoryTask.getSummary(), new TaskCollector() { + + @Override + public void collect(ITask task) { + synchronized (concurrencyLevel) { + ++concurrencyLevel[0]; + if (concurrencyLevel[0] < nThreads) { + try { + concurrencyLevel.wait(5000L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } else { + concurrencyLevel.notifyAll(); + } + } + ++hitCount[0]; + } + }, 100); + return hitCount[0] == 1; + } + }); + } + List<Future<Object>> futures = executorService.invokeAll(tasks); + for (Future<Object> future : futures) { + assertEquals(Boolean.TRUE, future.get()); + } + Assert.assertEquals(nThreads, concurrencyLevel[0]); + } finally { + executorService.shutdownNow(); + } + } + + @Test + public void testRepositoryUrlChanged() throws InterruptedException, CoreException { + setupIndex(); + + ITask repositoryTask = context.createRepositoryTask(); + final String originalHandle = repositoryTask.getHandleIdentifier(); + + index.waitUntilIdle(); + + final String newUrl = context.getMockRepository().getRepositoryUrl() + "/changed"; + + context.refactorMockRepositoryUrl(newUrl); + + Assert.assertFalse(originalHandle.equals(repositoryTask.getHandleIdentifier())); + + index.waitUntilIdle(); + + Assert.assertTrue(index.matches(repositoryTask, + IndexField.IDENTIFIER.fieldName() + ":" + index.escapeFieldValue(repositoryTask.getHandleIdentifier()))); + } + +} diff --git a/org.eclipse.mylyn.tasks.index.tests/src/org/eclipse/mylyn/internal/tasks/index/tests/util/MockTestContext.java b/org.eclipse.mylyn.tasks.index.tests/src/org/eclipse/mylyn/internal/tasks/index/tests/util/MockTestContext.java index 6a8cf28ab..8921cb045 100644 --- a/org.eclipse.mylyn.tasks.index.tests/src/org/eclipse/mylyn/internal/tasks/index/tests/util/MockTestContext.java +++ b/org.eclipse.mylyn.tasks.index.tests/src/org/eclipse/mylyn/internal/tasks/index/tests/util/MockTestContext.java @@ -1,177 +1,177 @@ -/*******************************************************************************
- * Copyright (c) 2011, 2012 Tasktop Technologies and others.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Tasktop Technologies - initial API and implementation
- *******************************************************************************/
-
-package org.eclipse.mylyn.internal.tasks.index.tests.util;
-
-import java.util.Date;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.mylyn.commons.core.DelegatingProgressMonitor;
-import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants;
-import org.eclipse.mylyn.internal.tasks.core.LocalRepositoryConnector;
-import org.eclipse.mylyn.internal.tasks.core.LocalTask;
-import org.eclipse.mylyn.internal.tasks.core.RepositoryModel;
-import org.eclipse.mylyn.internal.tasks.core.TaskActivityManager;
-import org.eclipse.mylyn.internal.tasks.core.TaskList;
-import org.eclipse.mylyn.internal.tasks.core.TaskRepositoryManager;
-import org.eclipse.mylyn.internal.tasks.core.data.SynchronizationManger;
-import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManager;
-import org.eclipse.mylyn.internal.tasks.core.data.TaskDataStore;
-import org.eclipse.mylyn.tasks.core.ITask;
-import org.eclipse.mylyn.tasks.core.TaskMapping;
-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.TaskData;
-import org.eclipse.mylyn.tasks.tests.connector.MockRepositoryConnector;
-import org.eclipse.mylyn.tasks.tests.connector.MockTask;
-
-/**
- * @author David Green
- */
-public class MockTestContext {
-
- private final TaskList taskList;
-
- private final TaskRepositoryManager repositoryManager;
-
- private final TaskDataManager dataManager;
-
- private final TaskDataStore dataStore;
-
- private final TaskActivityManager activityManager;
-
- private final SynchronizationManger synchronizationManger;
-
- private final RepositoryModel repositoryModel;
-
- private final TaskRepository mockRepository;
-
- private final TaskRepository localRepository;
-
- private final AtomicInteger idSeed = new AtomicInteger(1003);
-
- private final FullMockRepositoryConnector mockRepositoryConnector;
-
- public MockTestContext() {
- taskList = new TaskList();
- repositoryManager = new TaskRepositoryManager();
-
- mockRepositoryConnector = new FullMockRepositoryConnector();
- repositoryManager.addRepositoryConnector(mockRepositoryConnector);
- mockRepository = new TaskRepository(MockRepositoryConnector.CONNECTOR_KIND,
- MockRepositoryConnector.REPOSITORY_URL);
- repositoryManager.addRepository(mockRepository);
-
- repositoryManager.addRepositoryConnector(new LocalRepositoryConnector());
- localRepository = new TaskRepository(LocalRepositoryConnector.CONNECTOR_KIND,
- LocalRepositoryConnector.REPOSITORY_URL);
- repositoryManager.addRepository(localRepository);
-
- dataStore = new TaskDataStore(repositoryManager);
- activityManager = new TaskActivityManager(repositoryManager, taskList);
- repositoryModel = new RepositoryModel(taskList, repositoryManager);
- synchronizationManger = new SynchronizationManger(repositoryModel);
- dataManager = new TaskDataManager(dataStore, repositoryManager, taskList, activityManager,
- synchronizationManger);
-
- }
-
- public TaskList getTaskList() {
- return taskList;
- }
-
- public TaskRepositoryManager getRepositoryManager() {
- return repositoryManager;
- }
-
- public TaskDataManager getDataManager() {
- return dataManager;
- }
-
- public TaskDataStore getDataStore() {
- return dataStore;
- }
-
- public TaskActivityManager getActivityManager() {
- return activityManager;
- }
-
- public SynchronizationManger getSynchronizationManger() {
- return synchronizationManger;
- }
-
- public RepositoryModel getRepositoryModel() {
- return repositoryModel;
- }
-
- public ITask createLocalTask() {
- LocalTask task = new LocalTask(Integer.toString(idSeed.incrementAndGet()), "summary");
- task.setNotes("description " + task.getTaskKey());
-
- taskList.addTask(task);
-
- return task;
- }
-
- public ITask createRepositoryTask() throws CoreException {
- MockTask task = new MockTask(Integer.toString(idSeed.incrementAndGet()));
-
- TaskData taskData = new TaskData(new TaskAttributeMapper(mockRepository), task.getConnectorKind(),
- task.getRepositoryUrl(), task.getTaskId());
-
- mockRepositoryConnector.getTaskDataHandler().initializeTaskData(mockRepository, taskData, new TaskMapping(),
- new NullProgressMonitor());
-
- taskData.getRoot().getMappedAttribute(TaskAttribute.SUMMARY).setValue("summary");
- taskData.getRoot()
- .getMappedAttribute(TaskAttribute.DATE_CREATION)
- .setValue(Long.toString(new Date().getTime()));
- taskData.getRoot().getMappedAttribute(TaskAttribute.USER_REPORTER).setValue("reporter@example.com");
- taskData.getRoot().getMappedAttribute(TaskAttribute.USER_ASSIGNED).setValue("assignee@example.com");
- taskData.getRoot()
- .getMappedAttribute(TaskAttribute.DESCRIPTION)
- .setValue("task description " + task.getTaskKey());
-
- mockRepositoryConnector.getTaskMapping(taskData).applyTo(task);
-
- dataManager.putSubmittedTaskData(task, taskData, new DelegatingProgressMonitor());
- taskList.addTask(task);
-
- return task;
- }
-
- public TaskRepository getMockRepository() {
- return mockRepository;
- }
-
- public FullMockRepositoryConnector getMockRepositoryConnector() {
- return mockRepositoryConnector;
- }
-
- public void refactorMockRepositoryUrl(String newUrl) throws CoreException {
- String oldUrl = getMockRepository().getRepositoryUrl();
-
- for (ITask task : getTaskList().getAllTasks()) {
- if (oldUrl.equals(task.getAttribute(ITasksCoreConstants.ATTRIBUTE_OUTGOING_NEW_REPOSITORY_URL))) {
- getDataManager().refactorRepositoryUrl(task, task.getRepositoryUrl(), newUrl);
- }
- if (task.getRepositoryUrl().equals(oldUrl)) {
- getDataManager().refactorRepositoryUrl(task, newUrl, newUrl);
- }
- }
- getTaskList().refactorRepositoryUrl(oldUrl, newUrl);
- getMockRepository().setRepositoryUrl(newUrl);
- getRepositoryManager().notifyRepositoryUrlChanged(getMockRepository(), oldUrl);
- }
-}
+/******************************************************************************* + * Copyright (c) 2011, 2012 Tasktop Technologies and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ + +package org.eclipse.mylyn.internal.tasks.index.tests.util; + +import java.util.Date; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.mylyn.commons.core.DelegatingProgressMonitor; +import org.eclipse.mylyn.internal.tasks.core.ITasksCoreConstants; +import org.eclipse.mylyn.internal.tasks.core.LocalRepositoryConnector; +import org.eclipse.mylyn.internal.tasks.core.LocalTask; +import org.eclipse.mylyn.internal.tasks.core.RepositoryModel; +import org.eclipse.mylyn.internal.tasks.core.TaskActivityManager; +import org.eclipse.mylyn.internal.tasks.core.TaskList; +import org.eclipse.mylyn.internal.tasks.core.TaskRepositoryManager; +import org.eclipse.mylyn.internal.tasks.core.data.SynchronizationManger; +import org.eclipse.mylyn.internal.tasks.core.data.TaskDataManager; +import org.eclipse.mylyn.internal.tasks.core.data.TaskDataStore; +import org.eclipse.mylyn.tasks.core.ITask; +import org.eclipse.mylyn.tasks.core.TaskMapping; +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.TaskData; +import org.eclipse.mylyn.tasks.tests.connector.MockRepositoryConnector; +import org.eclipse.mylyn.tasks.tests.connector.MockTask; + +/** + * @author David Green + */ +public class MockTestContext { + + private final TaskList taskList; + + private final TaskRepositoryManager repositoryManager; + + private final TaskDataManager dataManager; + + private final TaskDataStore dataStore; + + private final TaskActivityManager activityManager; + + private final SynchronizationManger synchronizationManger; + + private final RepositoryModel repositoryModel; + + private final TaskRepository mockRepository; + + private final TaskRepository localRepository; + + private final AtomicInteger idSeed = new AtomicInteger(1003); + + private final FullMockRepositoryConnector mockRepositoryConnector; + + public MockTestContext() { + taskList = new TaskList(); + repositoryManager = new TaskRepositoryManager(); + + mockRepositoryConnector = new FullMockRepositoryConnector(); + repositoryManager.addRepositoryConnector(mockRepositoryConnector); + mockRepository = new TaskRepository(MockRepositoryConnector.CONNECTOR_KIND, + MockRepositoryConnector.REPOSITORY_URL); + repositoryManager.addRepository(mockRepository); + + repositoryManager.addRepositoryConnector(new LocalRepositoryConnector()); + localRepository = new TaskRepository(LocalRepositoryConnector.CONNECTOR_KIND, + LocalRepositoryConnector.REPOSITORY_URL); + repositoryManager.addRepository(localRepository); + + dataStore = new TaskDataStore(repositoryManager); + activityManager = new TaskActivityManager(repositoryManager, taskList); + repositoryModel = new RepositoryModel(taskList, repositoryManager); + synchronizationManger = new SynchronizationManger(repositoryModel); + dataManager = new TaskDataManager(dataStore, repositoryManager, taskList, activityManager, + synchronizationManger); + + } + + public TaskList getTaskList() { + return taskList; + } + + public TaskRepositoryManager getRepositoryManager() { + return repositoryManager; + } + + public TaskDataManager getDataManager() { + return dataManager; + } + + public TaskDataStore getDataStore() { + return dataStore; + } + + public TaskActivityManager getActivityManager() { + return activityManager; + } + + public SynchronizationManger getSynchronizationManger() { + return synchronizationManger; + } + + public RepositoryModel getRepositoryModel() { + return repositoryModel; + } + + public ITask createLocalTask() { + LocalTask task = new LocalTask(Integer.toString(idSeed.incrementAndGet()), "summary"); + task.setNotes("description " + task.getTaskKey()); + + taskList.addTask(task); + + return task; + } + + public ITask createRepositoryTask() throws CoreException { + MockTask task = new MockTask(Integer.toString(idSeed.incrementAndGet())); + + TaskData taskData = new TaskData(new TaskAttributeMapper(mockRepository), task.getConnectorKind(), + task.getRepositoryUrl(), task.getTaskId()); + + mockRepositoryConnector.getTaskDataHandler().initializeTaskData(mockRepository, taskData, new TaskMapping(), + new NullProgressMonitor()); + + taskData.getRoot().getMappedAttribute(TaskAttribute.SUMMARY).setValue("summary"); + taskData.getRoot() + .getMappedAttribute(TaskAttribute.DATE_CREATION) + .setValue(Long.toString(new Date().getTime())); + taskData.getRoot().getMappedAttribute(TaskAttribute.USER_REPORTER).setValue("reporter@example.com"); + taskData.getRoot().getMappedAttribute(TaskAttribute.USER_ASSIGNED).setValue("assignee@example.com"); + taskData.getRoot() + .getMappedAttribute(TaskAttribute.DESCRIPTION) + .setValue("task description " + task.getTaskKey()); + + mockRepositoryConnector.getTaskMapping(taskData).applyTo(task); + + dataManager.putSubmittedTaskData(task, taskData, new DelegatingProgressMonitor()); + taskList.addTask(task); + + return task; + } + + public TaskRepository getMockRepository() { + return mockRepository; + } + + public FullMockRepositoryConnector getMockRepositoryConnector() { + return mockRepositoryConnector; + } + + public void refactorMockRepositoryUrl(String newUrl) throws CoreException { + String oldUrl = getMockRepository().getRepositoryUrl(); + + for (ITask task : getTaskList().getAllTasks()) { + if (oldUrl.equals(task.getAttribute(ITasksCoreConstants.ATTRIBUTE_OUTGOING_NEW_REPOSITORY_URL))) { + getDataManager().refactorRepositoryUrl(task, task.getRepositoryUrl(), newUrl); + } + if (task.getRepositoryUrl().equals(oldUrl)) { + getDataManager().refactorRepositoryUrl(task, newUrl, newUrl); + } + } + getTaskList().refactorRepositoryUrl(oldUrl, newUrl); + getMockRepository().setRepositoryUrl(newUrl); + getRepositoryManager().notifyRepositoryUrlChanged(getMockRepository(), oldUrl); + } +} diff --git a/org.eclipse.mylyn.tasks.index.ui/src/org/eclipse/mylyn/internal/tasks/index/ui/IndexSearchHandler.java b/org.eclipse.mylyn.tasks.index.ui/src/org/eclipse/mylyn/internal/tasks/index/ui/IndexSearchHandler.java index cca957b67..2670647c4 100644 --- a/org.eclipse.mylyn.tasks.index.ui/src/org/eclipse/mylyn/internal/tasks/index/ui/IndexSearchHandler.java +++ b/org.eclipse.mylyn.tasks.index.ui/src/org/eclipse/mylyn/internal/tasks/index/ui/IndexSearchHandler.java @@ -1,256 +1,256 @@ -/*******************************************************************************
- * Copyright (c) 2011, 2012 Tasktop Technologies.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- * Tasktop Technologies - initial API and implementation
- *******************************************************************************/
-package org.eclipse.mylyn.internal.tasks.index.ui;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.List;
-import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.eclipse.jface.fieldassist.ContentProposalAdapter;
-import org.eclipse.jface.fieldassist.IContentProposal;
-import org.eclipse.jface.fieldassist.IContentProposalProvider;
-import org.eclipse.jface.fieldassist.TextContentAdapter;
-import org.eclipse.jface.layout.GridLayoutFactory;
-import org.eclipse.mylyn.internal.tasks.core.AbstractTask;
-import org.eclipse.mylyn.internal.tasks.index.core.TaskListIndex;
-import org.eclipse.mylyn.internal.tasks.index.core.TaskListIndex.IndexField;
-import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin;
-import org.eclipse.mylyn.internal.tasks.ui.search.AbstractSearchHandler;
-import org.eclipse.mylyn.tasks.core.IRepositoryManager;
-import org.eclipse.osgi.util.NLS;
-import org.eclipse.swt.SWT;
-import org.eclipse.swt.events.SelectionAdapter;
-import org.eclipse.swt.events.SelectionEvent;
-import org.eclipse.swt.widgets.Button;
-import org.eclipse.swt.widgets.Composite;
-import org.eclipse.swt.widgets.Text;
-import org.eclipse.ui.dialogs.PatternFilter;
-import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter;
-
-/**
- * @author David Green
- */
-public class IndexSearchHandler extends AbstractSearchHandler {
-
- private class ContentProposalProvider implements IContentProposalProvider {
- public IContentProposal[] getProposals(String contents, int position) {
- List<IContentProposal> proposals = new ArrayList<IContentProposal>(10);
-
- String fieldPrefix = ""; //$NON-NLS-1$
- String prefix = ""; //$NON-NLS-1$
- if (position >= 0 && position <= contents.length()) {
- int i = position;
- while (i > 0 && !Character.isWhitespace(contents.charAt(i - 1)) && contents.charAt(i - 1) != ':') {
- --i;
- }
- if (i > 0 && contents.charAt(i - 1) == ':') {
- int fieldEnd = i - 1;
- int fieldStart = i - 1;
- while (fieldStart > 0 && Character.isLetter(contents.charAt(fieldStart - 1))) {
- --fieldStart;
- }
- fieldPrefix = contents.substring(fieldStart, fieldEnd);
- }
-
- prefix = contents.substring(i, position);
- }
-
- // if we have a field prefix
- if (fieldPrefix.length() > 0) {
- IndexField indexField = null;
- try {
- indexField = IndexField.fromFieldName(fieldPrefix);
- } catch (IllegalArgumentException e) {
- }
-
- // if it's a person field then suggest
- // people from the task list
- if (indexField != null && indexField.isPersonField()) {
- computePersonProposals(proposals, prefix);
- }
-
- } else {
-
- // suggest field name prefixes
- for (IndexField field : IndexField.values()) {
-
- // searching on URL is not useful
- if (!field.isUserVisible()) {
- continue;
- }
-
- if (field.fieldName().startsWith(prefix)) {
- String description;
- switch (field) {
- case CONTENT:
- description = Messages.IndexSearchHandler_hint_content;
- break;
- case PERSON:
- description = Messages.IndexSearchHandler_hint_person;
- break;
- default:
- description = NLS.bind(Messages.IndexSearchHandler_hint_generic, field.fieldName());
- }
- proposals.add(new ContentProposal(field.fieldName().substring(prefix.length()) + ":", //$NON-NLS-1$
- field.fieldName(), description));
-
- if (field.isTypeDate()) {
- computeDateRangeProposals(proposals, field);
- }
- }
- }
- }
-
- return proposals.toArray(new IContentProposal[proposals.size()]);
- }
-
- public void computeDateRangeProposals(List<IContentProposal> proposals, IndexField field) {
- // for date fields give suggestion of date range search
- String description;
- final Date now = new Date();
- final Date dateSearchUpperBound;
- final Date dateSearchOneWeekLowerBound;
- {
- GregorianCalendar calendar = new GregorianCalendar();
-
- calendar.setTime(now);
- calendar.add(Calendar.DAY_OF_WEEK, 1); // one day in future due to GMT conversion in index
- dateSearchUpperBound = calendar.getTime();
-
- calendar.setTime(now);
- calendar.add(Calendar.DAY_OF_WEEK, -7);
- dateSearchOneWeekLowerBound = calendar.getTime();
- }
-
- description = NLS.bind(Messages.IndexSearchHandler_Generic_date_range_search_1_week, field.fieldName());
-
- String label = NLS.bind(Messages.IndexSearchHandler_Past_week_date_range_label, field.fieldName());
-
- String queryText = index.computeQueryFieldDateRange(field, dateSearchOneWeekLowerBound,
- dateSearchUpperBound);
- proposals.add(new ContentProposal(queryText, label, description));
- }
-
- public void computePersonProposals(List<IContentProposal> proposals, String prefix) {
- Set<String> addresses = new TreeSet<String>();
-
- Collection<AbstractTask> allTasks = TasksUiPlugin.getTaskList().getAllTasks();
- for (AbstractTask task : allTasks) {
- addAddresses(addresses, task);
- }
-
- for (String address : addresses) {
- if (address.startsWith(prefix)) {
- proposals.add(new ContentProposal(address.substring(prefix.length()), address, null));
- }
- }
- }
-
- private void addAddresses(Set<String> addresses, AbstractTask task) {
- String name = task.getOwner();
- if (name != null && name.trim().length() > 0) {
- addresses.add(name.trim());
- }
- }
- }
-
- private static TaskListIndex theIndex;
-
- private static AtomicInteger referenceCount = new AtomicInteger();
-
- /**
- * When not null serves as flag indicating that theIndex is referenced, thus preventing bad behaviour if dispose is
- * called multiple times.
- */
- private TaskListIndex index;
-
- public IndexSearchHandler() {
- }
-
- @Override
- public Composite createSearchComposite(Composite parent) {
- Composite container = new Composite(parent, SWT.NULL);
- GridLayoutFactory.swtDefaults().applyTo(container);
-
- final Button button = new Button(container, SWT.CHECK);
- button.setText(Messages.IndexSearchHandler_summaryOnly);
- button.setToolTipText(Messages.IndexSearchHandler_summaryOnly_tooltip);
- button.setSelection(true);
-
- button.addSelectionListener(new SelectionAdapter() {
- @Override
- public void widgetSelected(SelectionEvent e) {
- IndexField newDefaultField = button.getSelection() ? IndexField.SUMMARY : IndexField.CONTENT;
- index.setDefaultField(newDefaultField);
- fireFilterChanged();
- }
- });
-
- return container;
- }
-
- @Override
- public PatternFilter createFilter() {
- synchronized (IndexSearchHandler.class) {
- if (index == null) {
- if (theIndex == null) {
- final IRepositoryManager repositoryManager = TasksUiPlugin.getRepositoryManager();
- final File indexLocation = new File(TasksUiPlugin.getDefault().getDataDirectory(), ".taskListIndex"); //$NON-NLS-1$
-
- theIndex = new TaskListIndex(TasksUiPlugin.getTaskList(), TasksUiPlugin.getTaskDataManager(),
- repositoryManager, indexLocation);
-
- }
- index = theIndex;
- referenceCount.incrementAndGet();
- }
- }
- return new IndexedSubstringPatternFilter(index);
- }
-
- @Override
- public void adaptTextSearchControl(Text textControl) {
- IContentProposalProvider proposalProvider = new ContentProposalProvider();
- ContentAssistCommandAdapter adapter = new ContentAssistCommandAdapter(textControl, new TextContentAdapter(),
- proposalProvider, null, new char[0]);
- adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_INSERT);
-
- // if we decorate the control it lets the user know that they can use content assist...
- // BUT it looks pretty bad.
-// ControlDecoration controlDecoration = new ControlDecoration(textControl, (SWT.TOP | SWT.LEFT));
-// controlDecoration.setShowOnlyOnFocus(true);
-// FieldDecoration contentProposalImage = FieldDecorationRegistry.getDefault().getFieldDecoration(
-// FieldDecorationRegistry.DEC_CONTENT_PROPOSAL);
-// controlDecoration.setImage(contentProposalImage.getImage());
- }
-
- @Override
- public void dispose() {
- synchronized (IndexSearchHandler.class) {
- if (index != null) {
- index = null;
-
- if (referenceCount.decrementAndGet() == 0) {
- theIndex.close();
- theIndex = null;
- }
- }
- }
- }
-
-}
+/******************************************************************************* + * Copyright (c) 2011, 2012 Tasktop Technologies. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tasktop Technologies - initial API and implementation + *******************************************************************************/ +package org.eclipse.mylyn.internal.tasks.index.ui; + +import java.io.File; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.jface.fieldassist.ContentProposalAdapter; +import org.eclipse.jface.fieldassist.IContentProposal; +import org.eclipse.jface.fieldassist.IContentProposalProvider; +import org.eclipse.jface.fieldassist.TextContentAdapter; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.mylyn.internal.tasks.core.AbstractTask; +import org.eclipse.mylyn.internal.tasks.index.core.TaskListIndex; +import org.eclipse.mylyn.internal.tasks.index.core.TaskListIndex.IndexField; +import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; +import org.eclipse.mylyn.internal.tasks.ui.search.AbstractSearchHandler; +import org.eclipse.mylyn.tasks.core.IRepositoryManager; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.dialogs.PatternFilter; +import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter; + +/** + * @author David Green + */ +public class IndexSearchHandler extends AbstractSearchHandler { + + private class ContentProposalProvider implements IContentProposalProvider { + public IContentProposal[] getProposals(String contents, int position) { + List<IContentProposal> proposals = new ArrayList<IContentProposal>(10); + + String fieldPrefix = ""; //$NON-NLS-1$ + String prefix = ""; //$NON-NLS-1$ + if (position >= 0 && position <= contents.length()) { + int i = position; + while (i > 0 && !Character.isWhitespace(contents.charAt(i - 1)) && contents.charAt(i - 1) != ':') { + --i; + } + if (i > 0 && contents.charAt(i - 1) == ':') { + int fieldEnd = i - 1; + int fieldStart = i - 1; + while (fieldStart > 0 && Character.isLetter(contents.charAt(fieldStart - 1))) { + --fieldStart; + } + fieldPrefix = contents.substring(fieldStart, fieldEnd); + } + + prefix = contents.substring(i, position); + } + + // if we have a field prefix + if (fieldPrefix.length() > 0) { + IndexField indexField = null; + try { + indexField = IndexField.fromFieldName(fieldPrefix); + } catch (IllegalArgumentException e) { + } + + // if it's a person field then suggest + // people from the task list + if (indexField != null && indexField.isPersonField()) { + computePersonProposals(proposals, prefix); + } + + } else { + + // suggest field name prefixes + for (IndexField field : IndexField.values()) { + + // searching on URL is not useful + if (!field.isUserVisible()) { + continue; + } + + if (field.fieldName().startsWith(prefix)) { + String description; + switch (field) { + case CONTENT: + description = Messages.IndexSearchHandler_hint_content; + break; + case PERSON: + description = Messages.IndexSearchHandler_hint_person; + break; + default: + description = NLS.bind(Messages.IndexSearchHandler_hint_generic, field.fieldName()); + } + proposals.add(new ContentProposal(field.fieldName().substring(prefix.length()) + ":", //$NON-NLS-1$ + field.fieldName(), description)); + + if (field.isTypeDate()) { + computeDateRangeProposals(proposals, field); + } + } + } + } + + return proposals.toArray(new IContentProposal[proposals.size()]); + } + + public void computeDateRangeProposals(List<IContentProposal> proposals, IndexField field) { + // for date fields give suggestion of date range search + String description; + final Date now = new Date(); + final Date dateSearchUpperBound; + final Date dateSearchOneWeekLowerBound; + { + GregorianCalendar calendar = new GregorianCalendar(); + + calendar.setTime(now); + calendar.add(Calendar.DAY_OF_WEEK, 1); // one day in future due to GMT conversion in index + dateSearchUpperBound = calendar.getTime(); + + calendar.setTime(now); + calendar.add(Calendar.DAY_OF_WEEK, -7); + dateSearchOneWeekLowerBound = calendar.getTime(); + } + + description = NLS.bind(Messages.IndexSearchHandler_Generic_date_range_search_1_week, field.fieldName()); + + String label = NLS.bind(Messages.IndexSearchHandler_Past_week_date_range_label, field.fieldName()); + + String queryText = index.computeQueryFieldDateRange(field, dateSearchOneWeekLowerBound, + dateSearchUpperBound); + proposals.add(new ContentProposal(queryText, label, description)); + } + + public void computePersonProposals(List<IContentProposal> proposals, String prefix) { + Set<String> addresses = new TreeSet<String>(); + + Collection<AbstractTask> allTasks = TasksUiPlugin.getTaskList().getAllTasks(); + for (AbstractTask task : allTasks) { + addAddresses(addresses, task); + } + + for (String address : addresses) { + if (address.startsWith(prefix)) { + proposals.add(new ContentProposal(address.substring(prefix.length()), address, null)); + } + } + } + + private void addAddresses(Set<String> addresses, AbstractTask task) { + String name = task.getOwner(); + if (name != null && name.trim().length() > 0) { + addresses.add(name.trim()); + } + } + } + + private static TaskListIndex theIndex; + + private static AtomicInteger referenceCount = new AtomicInteger(); + + /** + * When not null serves as flag indicating that theIndex is referenced, thus preventing bad behaviour if dispose is + * called multiple times. + */ + private TaskListIndex index; + + public IndexSearchHandler() { + } + + @Override + public Composite createSearchComposite(Composite parent) { + Composite container = new Composite(parent, SWT.NULL); + GridLayoutFactory.swtDefaults().applyTo(container); + + final Button button = new Button(container, SWT.CHECK); + button.setText(Messages.IndexSearchHandler_summaryOnly); + button.setToolTipText(Messages.IndexSearchHandler_summaryOnly_tooltip); + button.setSelection(true); + + button.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + IndexField newDefaultField = button.getSelection() ? IndexField.SUMMARY : IndexField.CONTENT; + index.setDefaultField(newDefaultField); + fireFilterChanged(); + } + }); + + return container; + } + + @Override + public PatternFilter createFilter() { + synchronized (IndexSearchHandler.class) { + if (index == null) { + if (theIndex == null) { + final IRepositoryManager repositoryManager = TasksUiPlugin.getRepositoryManager(); + final File indexLocation = new File(TasksUiPlugin.getDefault().getDataDirectory(), ".taskListIndex"); //$NON-NLS-1$ + + theIndex = new TaskListIndex(TasksUiPlugin.getTaskList(), TasksUiPlugin.getTaskDataManager(), + repositoryManager, indexLocation); + + } + index = theIndex; + referenceCount.incrementAndGet(); + } + } + return new IndexedSubstringPatternFilter(index); + } + + @Override + public void adaptTextSearchControl(Text textControl) { + IContentProposalProvider proposalProvider = new ContentProposalProvider(); + ContentAssistCommandAdapter adapter = new ContentAssistCommandAdapter(textControl, new TextContentAdapter(), + proposalProvider, null, new char[0]); + adapter.setProposalAcceptanceStyle(ContentProposalAdapter.PROPOSAL_INSERT); + + // if we decorate the control it lets the user know that they can use content assist... + // BUT it looks pretty bad. +// ControlDecoration controlDecoration = new ControlDecoration(textControl, (SWT.TOP | SWT.LEFT)); +// controlDecoration.setShowOnlyOnFocus(true); +// FieldDecoration contentProposalImage = FieldDecorationRegistry.getDefault().getFieldDecoration( +// FieldDecorationRegistry.DEC_CONTENT_PROPOSAL); +// controlDecoration.setImage(contentProposalImage.getImage()); + } + + @Override + public void dispose() { + synchronized (IndexSearchHandler.class) { + if (index != null) { + index = null; + + if (referenceCount.decrementAndGet() == 0) { + theIndex.close(); + theIndex = null; + } + } + } + } + +} |