/*
* @copyright 2013 Philip Warner
* @license GNU General Public License
*
* This file is part of Book Catalogue.
*
* Book Catalogue is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Book Catalogue is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Book Catalogue. If not, see <http://www.gnu.org/licenses/>.
*/
package com.eleybourn.bookcatalogue.backup;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Serializable;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import com.eleybourn.bookcatalogue.goodreads.api.XmlFilter;
import com.eleybourn.bookcatalogue.goodreads.api.XmlFilter.ElementContext;
import com.eleybourn.bookcatalogue.goodreads.api.XmlFilter.XmlHandler;
import com.eleybourn.bookcatalogue.goodreads.api.XmlResponseParser;
import com.eleybourn.bookcatalogue.utils.Base64;
import com.eleybourn.bookcatalogue.utils.Logger;
/**
* Utility functions for backup/restore code
*
* @author pjw
*/
public class BackupUtils {
/**
* Class to provide access to a subset of the methods of collections.
*
* @author pjw
*
* @param <T> Type of the collection key
*/
private interface CollectionAccessor<T> {
/** Get the collection of keys */
Set<T> keySet();
/** Get the object for the specified key */
Object get(T key);
/** Process the passed tring to store int the collection */
public void putItem(Bundle bundle, String key, String type, String value) throws IOException;
}
/**
* Collection accessor for bundles
*
* @author pjw
*
*/
private static class BundleAccessor implements CollectionAccessor<String> {
Bundle mBundle;
public BundleAccessor(Bundle b) {
mBundle = b;
}
@Override
public Set<String> keySet() {
return mBundle.keySet();
}
@Override
public Object get(String key) {
return mBundle.get(key);
}
@Override
public void putItem(Bundle bundle, String key, String type, String value) throws IOException {
if (type.equals(BackupUtils.TYPE_INTEGER)) {
mBundle.putInt(key, Integer.parseInt(value));
} else if (type.equals(BackupUtils.TYPE_LONG)) {
mBundle.putLong(key, Long.parseLong(value));
} else if (type.equals(BackupUtils.TYPE_FLOAT)) {
mBundle.putFloat(key, Float.parseFloat(value));
} else if (type.equals(BackupUtils.TYPE_DOUBLE)) {
mBundle.putDouble(key, Double.parseDouble(value));
} else if (type.equals(BackupUtils.TYPE_STRING)) {
mBundle.putString(key, value);
} else if (type.equals(BackupUtils.TYPE_BOOLEAN)) {
mBundle.putBoolean(key, Boolean.parseBoolean(value));
} else if (type.equals(BackupUtils.TYPE_SERIALIZABLE)) {
Serializable s = Base64.decode(value);
mBundle.putSerializable(key, s);
}
}
}
/**
* Collection accessor for SharedPreferences
*
* @author pjw
*
*/
private static class PreferencesAccessor implements CollectionAccessor<String> {
SharedPreferences mPrefs;
Map<String,?> mMap;
Editor mEditor;
public PreferencesAccessor(SharedPreferences p) {
mPrefs = p;
mMap = p.getAll();
}
public void beginEdit() {
mEditor = mPrefs.edit();
mEditor.clear();
}
public void endEdit() {
mEditor.commit();
mEditor = null;
}
@Override
public Set<String> keySet() {
return mMap.keySet();
}
@Override
public Object get(String key) {
return mMap.get(key);
}
@Override
public void putItem(Bundle bundle, String key, String type, String value) throws IOException {
if (type.equals(BackupUtils.TYPE_INTEGER)) {
mEditor.putInt(key, Integer.parseInt(value));
} else if (type.equals(BackupUtils.TYPE_LONG)) {
mEditor.putLong(key, Long.parseLong(value));
} else if (type.equals(BackupUtils.TYPE_FLOAT)) {
mEditor.putFloat(key, Float.parseFloat(value));
} else if (type.equals(BackupUtils.TYPE_STRING)) {
mEditor.putString(key, value);
} else if (type.equals(BackupUtils.TYPE_BOOLEAN)) {
mEditor.putBoolean(key, Boolean.parseBoolean(value));
} else {
throw new RuntimeException("Unable write data of type '" + type + "' to preferences");
}
}
}
/**
* Write preferences to an XML stream.
*
* @param out
* @param prefs
* @throws IOException
*/
public static void preferencesToXml(BufferedWriter out, SharedPreferences prefs) throws IOException {
PreferencesAccessor a = new PreferencesAccessor(prefs);
collectionToXml(out, a);
}
/**
* Read preferences from an XML stream.
*
* @param in
* @param prefs
* @throws IOException
*/
public static void preferencesFromXml(BufferedReader in, SharedPreferences prefs) throws IOException {
PreferencesAccessor a = new PreferencesAccessor(prefs);
a.beginEdit();
collectionFromXml(in, a);
a.endEdit();
}
/**
* Write Bundle to an XML stream.
*
* @param out
* @param bundle
* @throws IOException
*/
public static void bundleToXml(BufferedWriter out, Bundle bundle) throws IOException {
BundleAccessor a = new BundleAccessor(bundle);
collectionToXml(out, a);
}
/**
* Read Bundle from an XML stream.
*
* @param in
* @return Bundle
* @throws IOException
*/
public static Bundle bundleFromXml(BufferedReader in) throws IOException {
final Bundle bundle = new Bundle();
BundleAccessor a = new BundleAccessor(bundle);
collectionFromXml(in, a);
return bundle;
}
/**
* Internal routine to send the passed CollectionAccessor data to an XML file.
*
* @param out
* @param col
* @throws IOException
*/
private static void collectionToXml(BufferedWriter out, CollectionAccessor<String> col) throws IOException {
out.append("<collection>\n");
for(String key: col.keySet() ) {
String type;
String value;
Object o = col.get(key);
if (o instanceof Integer) {
type = BackupUtils.TYPE_INTEGER;
value = o.toString();
} else if (o instanceof Long) {
type = BackupUtils.TYPE_LONG;
value = o.toString();
} else if (o instanceof Float) {
type = BackupUtils.TYPE_FLOAT;
value = o.toString();
} else if (o instanceof Double) {
type = BackupUtils.TYPE_DOUBLE;
value = o.toString();
} else if (o instanceof String) {
type = BackupUtils.TYPE_STRING;
value = o.toString();
} else if (o instanceof Boolean) {
type = BackupUtils.TYPE_BOOLEAN;
value = o.toString();
} else if (o instanceof Serializable) {
type = BackupUtils.TYPE_SERIALIZABLE;
value = Base64.encodeObject((Serializable)o);
} else {
throw new RuntimeException("Unable write data of type '" + o.getClass().getSimpleName() + "' to XML");
}
out.append("<item name=\"" + key + "\" type=\"" + type + "\">" + value + "</item>\n");
}
out.append("</collection>\n");
}
/**
* Record to preservr data while parsing XML input
*
* @author pjw
*/
private static class ItemInfo {
public String name;
public String type;
}
/**
* Internal routine to update the passed CollectionAccessor from an XML file.
*
* @param in
* @param accessor
* @throws IOException
*/
private static void collectionFromXml(BufferedReader in, final CollectionAccessor<String> accessor) throws IOException {
final Bundle bundle = new Bundle();
XmlFilter rootFilter = null;
rootFilter = new XmlFilter("");
final ItemInfo info = new ItemInfo();
XmlFilter.buildFilter(rootFilter, "collection", "item")
.setStartAction(new XmlHandler(){
@Override
public void process(ElementContext context) {
info.name = context.attributes.getValue("name");
info.type = context.attributes.getValue("type");
}}, null)
.setEndAction(new XmlHandler(){
@Override
public void process(ElementContext context) {
try {
accessor.putItem(bundle, info.name, info.type, context.body);
} catch (IOException e) {
Logger.logError(e);
throw new RuntimeException("Unable to process XML entity " + info.name + " (" + info.type + ")", e);
}
}}, null);
XmlResponseParser handler = new XmlResponseParser(rootFilter);
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser;
try {
parser = factory.newSAXParser();
} catch (Exception e) {
Logger.logError(e);
throw new RuntimeException("Unable to create XML parser", e);
}
InputSource is = new InputSource();
is.setCharacterStream(in);
try {
parser.parse(is, handler);
} catch (SAXException e) {
Logger.logError(e);
throw new IOException("Malformed XML");
}
}
public static final String TYPE_INTEGER = "Int";
public static final String TYPE_LONG = "Long";
public static final String TYPE_DOUBLE = "Dbl";
public static final String TYPE_FLOAT = "Flt";
public static final String TYPE_BOOLEAN = "Bool";
public static final String TYPE_STRING = "Str";
public static final String TYPE_SERIALIZABLE = "Serial";
}