/* ********************************************************************** **
** Copyright notice **
** **
** (c) 2005-2009 RSSOwl Development Team **
** http://www.rssowl.org/ **
** **
** 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.rssowl.org/legal/epl-v10.html **
** **
** A copy is found in the file epl-v10.html and important notices to the **
** license from the team is found in the textfile LICENSE.txt distributed **
** in this package. **
** **
** This copyright notice MUST APPEAR in all copies of the file! **
** **
** Contributors: **
** RSSOwl Development Team - initial API and implementation **
** **
** ********************************************************************** */
package org.rssowl.core.internal.interpreter;
import static org.rssowl.core.internal.interpreter.OPMLConstants.RSSOWL_NS;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.rssowl.core.Owl;
import org.rssowl.core.internal.Activator;
import org.rssowl.core.internal.interpreter.OPMLConstants.Attributes;
import org.rssowl.core.internal.interpreter.OPMLConstants.Tag;
import org.rssowl.core.interpreter.ITypeExporter;
import org.rssowl.core.interpreter.InterpreterException;
import org.rssowl.core.persist.IBookMark;
import org.rssowl.core.persist.IFilterAction;
import org.rssowl.core.persist.IFolder;
import org.rssowl.core.persist.IFolderChild;
import org.rssowl.core.persist.ILabel;
import org.rssowl.core.persist.IMark;
import org.rssowl.core.persist.INewsBin;
import org.rssowl.core.persist.ISearch;
import org.rssowl.core.persist.ISearchCondition;
import org.rssowl.core.persist.ISearchFilter;
import org.rssowl.core.persist.ISearchMark;
import org.rssowl.core.persist.dao.DynamicDAO;
import org.rssowl.core.persist.pref.IPreferenceScope;
import org.rssowl.core.persist.pref.IPreferenceType;
import org.rssowl.core.persist.pref.Preference;
import org.rssowl.core.persist.pref.IPreferenceScope.Kind;
import org.rssowl.core.util.CoreUtils;
import org.rssowl.core.util.StringUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.Map.Entry;
/**
* Implementation of {@link ITypeExporter} for the OPML XML format.
*
* @author bpasero
*/
public class OPMLExporter implements ITypeExporter {
/* Default Encoding */
private static final String UTF_8 = "UTF-8"; //$NON-NLS-1$
/*
* @see org.rssowl.core.interpreter.ITypeExporter#exportTo(java.io.File,
* java.util.Collection, java.util.Set)
*/
public void exportTo(File destination, Collection<? extends IFolderChild> elements, Set<Options> options) throws InterpreterException {
Format format = Format.getPrettyFormat();
format.setEncoding(UTF_8);
XMLOutputter output = new XMLOutputter(format);
DateFormat dateFormat = DateFormat.getDateInstance();
Document document = new Document();
Element root = new Element(Tag.OPML.get());
root.setAttribute(Attributes.VERSION.get(), "1.1"); //$NON-NLS-1$
root.addNamespaceDeclaration(RSSOWL_NS);
document.setRootElement(root);
/* Head */
Element head = new Element(Tag.HEAD.get());
root.addContent(head);
Element title = new Element(Tag.TITLE.get());
title.setText(Messages.OPMLExporter_RSSOWL_SUBSCRIPTIONS);
head.addContent(title);
Element dateModified = new Element(Tag.DATE_MODIFIED.get());
dateModified.setText(new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z").format(new Date())); //$NON-NLS-1$
head.addContent(dateModified);
/* Body */
Element body = new Element(Tag.BODY.get());
root.addContent(body);
boolean exportPreferences = (options != null && options.contains(Options.EXPORT_PREFERENCES));
/* Export Folder Childs */
if (elements != null && !elements.isEmpty()) {
Map<IFolder, Element> mapFolderToElement = new HashMap<IFolder, Element>();
mapFolderToElement.put(null, body);
/* Ensure that all Parent Elements exist */
repairHierarchy(mapFolderToElement, elements, exportPreferences);
/* Now export Elements */
exportFolderChilds(mapFolderToElement, elements, exportPreferences, dateFormat);
}
/* Export Labels */
if (options != null && options.contains(Options.EXPORT_LABELS))
exportLabels(body);
/* Export Filters */
if (options != null && options.contains(Options.EXPORT_FILTERS))
exportFilters(body, dateFormat);
/* Export Preferences */
if (exportPreferences)
exportPreferences(body);
/* Write to File */
FileOutputStream out = null;
try {
out = new FileOutputStream(destination);
output.output(document, out);
out.close();
} catch (FileNotFoundException e) {
throw new InterpreterException(Activator.getDefault().createErrorStatus(e.getMessage(), e));
} catch (IOException e) {
throw new InterpreterException(Activator.getDefault().createErrorStatus(e.getMessage(), e));
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
throw new InterpreterException(Activator.getDefault().createErrorStatus(e.getMessage(), e));
}
}
}
}
private void repairHierarchy(Map<IFolder, Element> mapFolderToElement, Collection<? extends IFolderChild> elementsToExport, boolean exportPreferences) {
/* Retrieve all Parents */
Set<IFolder> allParentFolders = new HashSet<IFolder>();
for (IFolderChild element : elementsToExport) {
fillParents(allParentFolders, element);
}
/* Return if Hierarchy is already consistent */
if (allParentFolders.isEmpty())
return;
/* Create Elements for all Parents */
for (IFolder parent : allParentFolders) {
Element folderElement = createElement(parent, exportPreferences);
mapFolderToElement.put(parent, folderElement);
}
/* Connect all Parent Nodes according to hierarchy */
for (IFolder parent : allParentFolders) {
Element folderElement = mapFolderToElement.get(parent);
Element parentElement = mapFolderToElement.get(parent.getParent());
parentElement.addContent(folderElement);
}
}
private void fillParents(Set<IFolder> parents, IFolderChild child) {
IFolder parent = child.getParent();
if (parent != null) {
parents.add(parent);
fillParents(parents, parent);
}
}
private void exportFolderChilds(Map<IFolder, Element> mapFolderToElement, Collection<? extends IFolderChild> childs, boolean exportPreferences, DateFormat df) {
for (IFolderChild child : childs) {
/* Export Folder */
if (child instanceof IFolder)
exportFolder(mapFolderToElement, (IFolder) child, exportPreferences, df);
/* Export Bookmark, Search or Bin */
else if (child instanceof IMark)
exportMark(mapFolderToElement, (IMark) child, exportPreferences, df);
}
}
private Element createElement(IFolder folder, boolean exportPreferences) {
Element folderElement = new Element(Tag.OUTLINE.get());
folderElement.setAttribute(Attributes.TEXT.get(), folder.getName());
folderElement.setAttribute(Attributes.IS_SET.get(), String.valueOf(folder.getParent() == null), RSSOWL_NS);
folderElement.setAttribute(Attributes.ID.get(), String.valueOf(folder.getId()), RSSOWL_NS);
/* Export Preferences if set */
if (exportPreferences)
exportPreferences(folderElement, folder);
return folderElement;
}
private void exportFolder(Map<IFolder, Element> mapFolderToElement, IFolder folder, boolean exportPreferences, DateFormat df) {
Element folderElement = createElement(folder, exportPreferences);
/* Store in Map for Childs to Use */
mapFolderToElement.put(folder, folderElement);
/* Connect Node according to hierarchy */
mapFolderToElement.get(folder.getParent()).addContent(folderElement);
/* Proceed with Folder Childs */
exportFolderChilds(mapFolderToElement, folder.getChildren(), exportPreferences, df);
}
private void exportPreferences(Element parent, IFolderChild child) {
Map<String, Serializable> properties = child.getProperties();
if (properties != null) {
Set<Entry<String, Serializable>> entries = properties.entrySet();
for (Entry<String, Serializable> entry : entries) {
if (StringUtils.isSet(entry.getKey()) && entry.getValue() != null) {
String value = getValueAsString(entry.getValue());
if (value != null) {
Element prefElement = new Element(Tag.PREFERENCE.get(), RSSOWL_NS);
prefElement.setAttribute(Attributes.ID.get(), entry.getKey());
prefElement.setAttribute(Attributes.VALUE.get(), value);
prefElement.setAttribute(Attributes.TYPE.get(), String.valueOf(IPreferenceType.getType(entry.getValue()).ordinal()));
parent.addContent(prefElement);
}
}
}
}
}
private String getValueAsString(Object property) {
if (property instanceof String)
return (String) property;
if (property instanceof Long || property instanceof Integer || property instanceof Boolean)
return String.valueOf(property);
if (property instanceof long[]) {
long[] values = (long[]) property;
StringBuilder builder = new StringBuilder();
for (long val : values) {
builder.append(val).append(OPMLConstants.SEPARATOR);
}
if (values.length > 0)
builder.delete(builder.length() - 1, builder.length());
return builder.toString();
}
if (property instanceof int[]) {
int[] values = (int[]) property;
StringBuilder builder = new StringBuilder();
for (int val : values) {
builder.append(val).append(OPMLConstants.SEPARATOR);
}
if (values.length > 0)
builder.delete(builder.length() - 1, builder.length());
return builder.toString();
}
if (property instanceof String[]) {
String[] values = (String[]) property;
StringBuilder builder = new StringBuilder();
for (String val : values) {
builder.append(val).append(OPMLConstants.SEPARATOR);
}
if (values.length > 0)
builder.delete(builder.length() - 1, builder.length());
return builder.toString();
}
return null;
}
private void exportMark(Map<IFolder, Element> mapFolderToElement, IMark mark, boolean exportPreferences, DateFormat df) {
String name = mark.getName();
Element element = null;
/* Export BookMark */
if (mark instanceof IBookMark) {
String link = ((IBookMark) mark).getFeedLinkReference().getLinkAsText();
element = new Element(Tag.OUTLINE.get());
element.setAttribute(Attributes.TEXT.get(), name);
element.setAttribute(Attributes.XML_URL.get(), link);
element.setAttribute(Attributes.ID.get(), String.valueOf(mark.getId()), RSSOWL_NS);
mapFolderToElement.get(mark.getParent()).addContent(element);
}
/* Export SearchMark */
else if (mark instanceof ISearchMark) {
ISearchMark searchMark = (ISearchMark) mark;
List<ISearchCondition> conditions = searchMark.getSearchConditions();
element = new Element(Tag.SAVED_SEARCH.get(), RSSOWL_NS);
element.setAttribute(Attributes.NAME.get(), name);
element.setAttribute(Attributes.MATCH_ALL_CONDITIONS.get(), String.valueOf(searchMark.matchAllConditions()));
element.setAttribute(Attributes.ID.get(), String.valueOf(mark.getId()), RSSOWL_NS);
mapFolderToElement.get(mark.getParent()).addContent(element);
for (ISearchCondition condition : conditions) {
Element conditionElement = new Element(Tag.SEARCH_CONDITION.get(), RSSOWL_NS);
element.addContent(conditionElement);
if (condition.getValue() != null)
fillElement(conditionElement, condition, df);
}
}
/* Export Newsbin */
else if (mark instanceof INewsBin) {
element = new Element(Tag.BIN.get(), RSSOWL_NS);
element.setAttribute(Attributes.NAME.get(), name);
element.setAttribute(Attributes.ID.get(), String.valueOf(mark.getId()), RSSOWL_NS);
mapFolderToElement.get(mark.getParent()).addContent(element);
}
/* Export Preferences if set */
if (element != null && exportPreferences)
exportPreferences(element, mark);
}
private void exportFilters(Element body, DateFormat df) {
Collection<ISearchFilter> filters = DynamicDAO.loadAll(ISearchFilter.class);
for (ISearchFilter filter : filters) {
String name = filter.getName();
int order = filter.getOrder();
boolean isEnabled = filter.isEnabled();
boolean matchAllNews = filter.matchAllNews();
Element filterElement = new Element(Tag.FILTER.get(), RSSOWL_NS);
filterElement.setAttribute(Attributes.NAME.get(), name);
filterElement.setAttribute(Attributes.ORDER.get(), String.valueOf(order));
filterElement.setAttribute(Attributes.ENABLED.get(), String.valueOf(isEnabled));
filterElement.setAttribute(Attributes.MATCH_ALL_NEWS.get(), String.valueOf(matchAllNews));
body.addContent(filterElement);
/* Export Search if provided */
ISearch search = filter.getSearch();
if (search != null) {
List<ISearchCondition> conditions = search.getSearchConditions();
Element searchElement = new Element(Tag.SEARCH.get(), RSSOWL_NS);
searchElement.setAttribute(Attributes.MATCH_ALL_CONDITIONS.get(), String.valueOf(search.matchAllConditions()));
filterElement.addContent(searchElement);
for (ISearchCondition condition : conditions) {
Element conditionElement = new Element(Tag.SEARCH_CONDITION.get(), RSSOWL_NS);
searchElement.addContent(conditionElement);
if (condition.getValue() != null)
fillElement(conditionElement, condition, df);
}
}
/* Export Actions */
List<IFilterAction> actions = filter.getActions();
for (IFilterAction action : actions) {
String actionId = action.getActionId();
Element actionElement = new Element(Tag.ACTION.get(), RSSOWL_NS);
actionElement.setAttribute(Attributes.ID.get(), actionId);
/* Action Data as Properties */
if (action.getData() instanceof Properties) {
Properties data = (Properties) action.getData();
Set<Entry<Object, Object>> entries = data.entrySet();
for (Entry<Object, Object> entry : entries) {
String key = entry.getKey().toString();
String value = entry.getValue().toString();
Element actionProperty = new Element(Tag.ACTION_PROPERTY.get(), RSSOWL_NS);
actionProperty.setAttribute(Attributes.ID.get(), key);
actionProperty.setAttribute(Attributes.VALUE.get(), value);
actionElement.addContent(actionProperty);
}
}
/* Action Data as Primitive */
else {
String data = toString(action.getData());
if (data != null)
actionElement.setAttribute(Attributes.DATA.get(), data);
}
filterElement.addContent(actionElement);
}
}
}
private void fillElement(Element conditionElement, ISearchCondition condition, DateFormat df) {
/* Search Specifier */
Element searchSpecifier = new Element(Tag.SPECIFIER.get(), RSSOWL_NS);
searchSpecifier.setAttribute(Attributes.ID.get(), String.valueOf(condition.getSpecifier().ordinal()));
conditionElement.addContent(searchSpecifier);
/* Search Condition: Location */
if (condition.getValue() instanceof Long[][]) {
List<IFolderChild> locations = CoreUtils.toEntities((Long[][]) condition.getValue());
Element searchValue = new Element(Tag.SEARCH_VALUE.get(), RSSOWL_NS);
searchValue.setAttribute(Attributes.TYPE.get(), String.valueOf(condition.getField().getSearchValueType().getId()));
conditionElement.addContent(searchValue);
for (IFolderChild child : locations) {
boolean isFolder = (child instanceof IFolder);
boolean isNewsbin = (child instanceof INewsBin);
Element location = new Element(Tag.LOCATION.get(), RSSOWL_NS);
location.setAttribute(Attributes.IS_BIN.get(), String.valueOf(isNewsbin));
location.setAttribute(Attributes.IS_FOLDER.get(), String.valueOf(isFolder));
location.setAttribute(Attributes.VALUE.get(), String.valueOf(child.getId()));
searchValue.addContent(location);
}
}
/* Single Value */
else if (!EnumSet.class.isAssignableFrom(condition.getValue().getClass())) {
Element searchValue = new Element(Tag.SEARCH_VALUE.get(), RSSOWL_NS);
searchValue.setAttribute(Attributes.TYPE.get(), String.valueOf(condition.getField().getSearchValueType().getId()));
searchValue.setAttribute(Attributes.VALUE.get(), getValueString(df, condition));
conditionElement.addContent(searchValue);
}
/* Multiple Values */
else {
EnumSet<?> values = ((EnumSet<?>) condition.getValue());
Element searchValue = new Element(Tag.SEARCH_VALUE.get(), RSSOWL_NS);
searchValue.setAttribute(Attributes.TYPE.get(), String.valueOf(condition.getField().getSearchValueType().getId()));
conditionElement.addContent(searchValue);
for (Enum<?> enumValue : values) {
Element state = new Element(Tag.STATE.get(), RSSOWL_NS);
state.setAttribute(Attributes.VALUE.get(), String.valueOf(enumValue.ordinal()));
searchValue.addContent(state);
}
}
/* Search Field */
Element field = new Element(Tag.SEARCH_FIELD.get(), RSSOWL_NS);
field.setAttribute(Attributes.ID.get(), String.valueOf(condition.getField().getId()));
field.setAttribute(Attributes.ENTITY.get(), condition.getField().getEntityName());
conditionElement.addContent(field);
}
private String toString(Object data) {
if (data == null)
return null;
if (data instanceof String)
return (String) data;
if (data instanceof Long)
return String.valueOf(data);
if (data instanceof Long[]) {
Long[] value = (Long[]) data;
StringBuilder builder = new StringBuilder();
for (Long val : value) {
builder.append(val).append(OPMLConstants.SEPARATOR);
}
if (value.length > 0)
builder.delete(builder.length() - 1, builder.length());
return builder.toString();
}
return null;
}
private String getValueString(DateFormat df, ISearchCondition condition) {
if (condition.getValue() instanceof Date)
return df.format((Date) condition.getValue());
return condition.getValue().toString();
}
private void exportLabels(Element body) {
Collection<ILabel> labels = DynamicDAO.loadAll(ILabel.class);
for (ILabel label : labels) {
Long id = label.getId();
String name = label.getName();
String color = label.getColor();
int order = label.getOrder();
Element labelElement = new Element(Tag.LABEL.get(), RSSOWL_NS);
labelElement.setAttribute(Attributes.ID.get(), String.valueOf(id));
labelElement.setAttribute(Attributes.NAME.get(), name);
labelElement.setAttribute(Attributes.ORDER.get(), String.valueOf(order));
labelElement.setAttribute(Attributes.COLOR.get(), color);
body.addContent(labelElement);
}
}
private void exportPreferences(Element body) {
IPreferenceScope globalPreferences = Owl.getPreferenceService().getGlobalScope();
IPreferenceScope eclipsePreferences = Owl.getPreferenceService().getEclipseScope();
Preference[] preferences = Preference.values();
for (Preference preference : preferences) {
if (preference.getKind() == Kind.ENTITY)
continue;
String value = getValueAsString(preference, globalPreferences, eclipsePreferences);
if (value != null) {
Element prefElement = new Element(Tag.PREFERENCE.get(), RSSOWL_NS);
prefElement.setAttribute(Attributes.ID.get(), preference.id());
prefElement.setAttribute(Attributes.VALUE.get(), value);
prefElement.setAttribute(Attributes.TYPE.get(), String.valueOf(preference.getType().ordinal()));
prefElement.setAttribute(Attributes.KIND.get(), String.valueOf(preference.getKind().ordinal()));
body.addContent(prefElement);
}
}
}
private String getValueAsString(Preference preference, IPreferenceScope global, IPreferenceScope eclipse) {
IPreferenceScope actualScope = (preference.getKind() == Kind.GLOBAL) ? global : eclipse;
String id = preference.id();
switch (preference.getType()) {
case BOOLEAN:
return getValueAsString(actualScope.getBoolean(id));
case INTEGER:
return getValueAsString(actualScope.getInteger(id));
case INTEGERS:
return getValueAsString(actualScope.getIntegers(id));
case LONG:
return getValueAsString(actualScope.getLong(id));
case LONGS:
return getValueAsString(actualScope.getLongs(id));
case STRING:
return getValueAsString(actualScope.getString(id));
case STRINGS:
return getValueAsString(actualScope.getStrings(id));
}
return null;
}
}