blob: 7a1e38b6da41c264e319839c4a4bc9db37d5bcc5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Tasktop Technologies and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Tasktop Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.internal.tasks.core.externalization;
import java.util.Collection;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.internal.tasks.core.AbstractTask;
import org.eclipse.mylyn.internal.tasks.core.AbstractTaskCategory;
import org.eclipse.mylyn.internal.tasks.core.AbstractTaskContainer;
import org.eclipse.mylyn.internal.tasks.core.ITransferList;
import org.eclipse.mylyn.internal.tasks.core.RepositoryModel;
import org.eclipse.mylyn.internal.tasks.core.RepositoryQuery;
import org.eclipse.mylyn.tasks.core.IRepositoryElement;
import org.eclipse.mylyn.tasks.core.IRepositoryManager;
import org.w3c.dom.Document;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.google.common.base.Strings;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
public class SaxTaskListHandler extends DefaultHandler {
private final LazyTransferList taskList;
private final RepositoryModel repositoryModel;
private final IRepositoryManager repositoryManager;
private final Multimap<AbstractTask, String> subTasks;
private final Multimap<RepositoryQuery, String> queryResults;
private final Multimap<AbstractTaskCategory, String> categorizedTasks;
private SaxTaskListElementBuilder<? extends IRepositoryElement> currentBuilder;
private final SaxOrphanBuilder orphanBuilder;
public SaxTaskListHandler(ITransferList taskList, RepositoryModel repositoryModel,
IRepositoryManager repositoryManager) throws CoreException {
this.taskList = new LazyTransferList(taskList);
this.repositoryModel = repositoryModel;
this.repositoryManager = repositoryManager;
this.subTasks = HashMultimap.create();
this.queryResults = HashMultimap.create();
this.categorizedTasks = HashMultimap.create();
this.orphanBuilder = new SaxOrphanBuilder();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
switch (localName) {
case TaskListExternalizationConstants.NODE_TASK:
checkState(currentBuilder == null, "Cannot begin reading a task while reading another task list element."); //$NON-NLS-1$
currentBuilder = new SaxTaskBuilder(repositoryModel, repositoryManager);
currentBuilder.beginItem(attributes);
break;
case TaskListExternalizationConstants.NODE_QUERY:
checkState(currentBuilder == null, "Cannot begin reading a query while reading another task list element."); //$NON-NLS-1$
currentBuilder = new SaxQueryBuilder(repositoryModel, repositoryManager);
currentBuilder.beginItem(attributes);
break;
case TaskListExternalizationConstants.NODE_CATEGORY:
checkState(currentBuilder == null,
"Cannot begin reading a category while reading another task list element."); //$NON-NLS-1$
currentBuilder = new SaxCategoryBuilder(taskList);
currentBuilder.beginItem(attributes);
break;
case TaskListExternalizationConstants.NODE_ATTRIBUTE:
if (isOK(currentBuilder) && !currentBuilder.isAcceptingAttributeValues()) {
currentBuilder.startAttribute(attributes);
}
break;
case TaskListExternalizationConstants.NODE_SUB_TASK:
checkState(currentBuilder instanceof SaxTaskBuilder, "Cannot read a sub task hit unless reading a task"); //$NON-NLS-1$
recordHit(attributes, subTasks, (SaxTaskBuilder) currentBuilder);
break;
case TaskListExternalizationConstants.NODE_QUERY_HIT:
checkState(currentBuilder instanceof SaxQueryBuilder, "Cannot read a query hit unless reading a query"); //$NON-NLS-1$
recordHit(attributes, queryResults, (SaxQueryBuilder) currentBuilder);
break;
case TaskListExternalizationConstants.NODE_TASK_REFERENCE:
checkState(currentBuilder instanceof SaxCategoryBuilder,
"Cannot read a category hit unless reading a category"); //$NON-NLS-1$
recordHit(attributes, categorizedTasks, (SaxCategoryBuilder) currentBuilder);
break;
default:
break;
}
// don't attempt to make the root element an orphan
if (!TaskListExternalizationConstants.NODE_TASK_LIST.equals(localName)) {
orphanBuilder.startElement(localName, attributes);
}
}
@Override
public void characters(char ch[], int start, int length) throws SAXException {
if (isOK(currentBuilder) && currentBuilder.isAcceptingAttributeValues()) {
currentBuilder.acceptAttributeValueContent(ch, start, length);
}
orphanBuilder.acceptCharacters(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
switch (localName) {
case TaskListExternalizationConstants.NODE_TASK:
case TaskListExternalizationConstants.NODE_QUERY:
case TaskListExternalizationConstants.NODE_CATEGORY:
commitCurrentTopLevelElement();
currentBuilder = null;
break;
case TaskListExternalizationConstants.NODE_ATTRIBUTE:
if (isOK(currentBuilder) && currentBuilder.isAcceptingAttributeValues()) {
currentBuilder.endAttribute();
}
break;
case TaskListExternalizationConstants.NODE_TASK_LIST:
applyContainmentToTaskList(subTasks);
applyContainmentToTaskList(queryResults);
applyContainmentToTaskList(categorizedTasks);
commitUntransferedTasksToTaskList();
break;
default:
break;
}
orphanBuilder.endElement();
}
private void commitUntransferedTasksToTaskList() {
taskList.commit();
}
private <T extends IRepositoryElement> void recordHit(Attributes attributes, Multimap<T, String> hitMap,
SaxTaskListElementBuilder<T> builder) {
String handle = attributes.getValue(TaskListExternalizationConstants.KEY_HANDLE);
if (!Strings.isNullOrEmpty(handle) && isOK(builder)) {
hitMap.put(builder.getItem(), handle);
}
}
private void commitCurrentTopLevelElement() throws SAXException {
checkState(currentBuilder != null,
"Cannot finish reading a task list element without a corresponding builder."); //$NON-NLS-1$
if (isOK(currentBuilder)) {
currentBuilder.addToTaskList(taskList);
} else {
if (!currentBuilder.getErrors().isOK()) {
StatusHandler.log(currentBuilder.getErrors());
}
orphanBuilder.commitOrphan();
}
}
private <T extends AbstractTaskContainer> void applyContainmentToTaskList(Multimap<T, String> containment) {
for (T container : containment.keySet()) {
Collection<String> handles = containment.get(container);
for (String handle : handles) {
AbstractTask subTask = taskList.getTask(handle);
if (subTask != null) {
taskList.addTask(subTask, container);
}
}
}
}
private boolean isOK(SaxTaskListElementBuilder<?> builder) {
return builder != null && builder.getErrors().isOK() && builder.getItem() != null;
}
public Document getOrphans() {
return orphanBuilder.getOrphans();
}
private void checkState(boolean condition, String message) throws SAXException {
if (!condition) {
throw new SAXException(message);
}
}
}