/******************************************************************************* * 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 ITransferList 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 = 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); break; default: break; } orphanBuilder.endElement(); } 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); } } }