/com/antlersoft/android/contentxml/SqliteElement.java

http://sqlitegen.googlecode.com/ · Java · 539 lines · 351 code · 49 blank · 139 comment · 28 complexity · 277813ee7d4ebd2ea044fac41557c95d MD5 · raw file

  1. /**
  2. * Copyright (C) 2010 Michael A. MacDonald
  3. */
  4. package com.antlersoft.android.contentxml;
  5. import java.io.IOException;
  6. import java.io.Reader;
  7. import java.io.Writer;
  8. import java.util.ArrayList;
  9. import java.util.Stack;
  10. import android.content.ContentValues;
  11. import android.database.Cursor;
  12. import android.database.DatabaseUtils;
  13. import android.database.SQLException;
  14. import android.database.sqlite.SQLiteDatabase;
  15. import android.util.Xml;
  16. import org.xml.sax.Attributes;
  17. import org.xml.sax.ContentHandler;
  18. import org.xml.sax.Locator;
  19. import org.xml.sax.SAXException;
  20. import org.xml.sax.helpers.DefaultHandler;
  21. import org.xmlpull.v1.XmlSerializer;
  22. import com.antlersoft.util.xml.HandlerStack;
  23. import com.antlersoft.util.xml.IElement;
  24. import com.antlersoft.util.xml.IHandlerStack;
  25. import com.antlersoft.util.xml.SimpleAttributes;
  26. /**
  27. * Save or restore the contents of an entire SQLiteDatabase (or entire tables in a SQLite database)
  28. * as an XML element.
  29. * <p>
  30. * This assumes that SQLite table names and
  31. * SQLite column contents can be stored correctly in an XML attribute.
  32. * @author Michael A. MacDonald
  33. *
  34. */
  35. public class SqliteElement implements IElement {
  36. /**
  37. * android xml parsing does not use an XmlReader that
  38. * HandlerStack was designed around; instead we'll interpose
  39. * this content handler layer that manages a stack of
  40. * ContentHandlers.
  41. * @author Michael A. MacDonald
  42. *
  43. */
  44. public static class StackContentHandler implements ContentHandler, IHandlerStack {
  45. private Stack<ContentHandler> _stack = new Stack<ContentHandler>();
  46. /* (non-Javadoc)
  47. * @see com.antlersoft.util.xml.IHandlerStack#popHandlerStack()
  48. */
  49. @Override
  50. public void popHandlerStack() {
  51. _stack.pop();
  52. }
  53. /* (non-Javadoc)
  54. * @see com.antlersoft.util.xml.IHandlerStack#pushHandlerStack(org.xml.sax.helpers.DefaultHandler)
  55. */
  56. @Override
  57. public void pushHandlerStack(DefaultHandler handler) {
  58. _stack.push(handler);
  59. }
  60. /* (non-Javadoc)
  61. * @see com.antlersoft.util.xml.IHandlerStack#startWithHandler(org.xml.sax.helpers.DefaultHandler, java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
  62. */
  63. @Override
  64. public void startWithHandler(DefaultHandler handler, String uri,
  65. String localName, String qName, Attributes attributes)
  66. throws SAXException {
  67. _stack.push(handler);
  68. handler.startElement(uri, localName, qName, attributes);
  69. }
  70. /* (non-Javadoc)
  71. * @see org.xml.sax.ContentHandler#characters(char[], int, int)
  72. */
  73. @Override
  74. public void characters(char[] arg0, int arg1, int arg2)
  75. throws SAXException {
  76. _stack.peek().characters(arg0, arg1, arg2);
  77. }
  78. /* (non-Javadoc)
  79. * @see org.xml.sax.ContentHandler#endDocument()
  80. */
  81. @Override
  82. public void endDocument() throws SAXException {
  83. _stack.peek().endDocument();
  84. }
  85. /* (non-Javadoc)
  86. * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
  87. */
  88. @Override
  89. public void endElement(String arg0, String arg1, String arg2)
  90. throws SAXException {
  91. _stack.peek().endElement(arg0, arg1, arg2);
  92. }
  93. /* (non-Javadoc)
  94. * @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String)
  95. */
  96. @Override
  97. public void endPrefixMapping(String arg0) throws SAXException {
  98. _stack.peek().endPrefixMapping(arg0);
  99. }
  100. /* (non-Javadoc)
  101. * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
  102. */
  103. @Override
  104. public void ignorableWhitespace(char[] arg0, int arg1, int arg2)
  105. throws SAXException {
  106. _stack.peek().ignorableWhitespace(arg0, arg1, arg2);
  107. }
  108. /* (non-Javadoc)
  109. * @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String, java.lang.String)
  110. */
  111. @Override
  112. public void processingInstruction(String arg0, String arg1)
  113. throws SAXException {
  114. _stack.peek().processingInstruction(arg0, arg1);
  115. }
  116. /* (non-Javadoc)
  117. * @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator)
  118. */
  119. @Override
  120. public void setDocumentLocator(Locator arg0) {
  121. _stack.peek().setDocumentLocator(arg0);
  122. }
  123. /* (non-Javadoc)
  124. * @see org.xml.sax.ContentHandler#skippedEntity(java.lang.String)
  125. */
  126. @Override
  127. public void skippedEntity(String arg0) throws SAXException {
  128. _stack.peek().skippedEntity(arg0);
  129. }
  130. /* (non-Javadoc)
  131. * @see org.xml.sax.ContentHandler#startDocument()
  132. */
  133. @Override
  134. public void startDocument() throws SAXException {
  135. _stack.peek().startDocument();
  136. }
  137. /* (non-Javadoc)
  138. * @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
  139. */
  140. @Override
  141. public void startElement(String arg0, String arg1, String arg2,
  142. Attributes arg3) throws SAXException {
  143. _stack.peek().startElement(arg0, arg1, arg2, arg3);
  144. }
  145. /* (non-Javadoc)
  146. * @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String, java.lang.String)
  147. */
  148. @Override
  149. public void startPrefixMapping(String arg0, String arg1)
  150. throws SAXException {
  151. _stack.peek().startPrefixMapping(arg0, arg1);
  152. }
  153. }
  154. private ArrayList<String> _tableNames;
  155. SQLiteDatabase _db;
  156. private ReplaceStrategy _replaceStrategy;
  157. private String _databaseTag;
  158. static final String[] TABLE_ARRAY = new String[] { "name" };
  159. static final String TABLE_ELEMENT = "table";
  160. static final String TABLE_NAME_ATTRIBUTE = "table_name";
  161. static final String ROW_ELEMENT = "row";
  162. /**
  163. * Determines how existing rows in the table are handled when reading a row from XML causes
  164. * constraint violation.
  165. * @author Michael A. MacDonald
  166. *
  167. */
  168. public static enum ReplaceStrategy
  169. {
  170. /**
  171. * All existing rows are dropped from the table before any rows are added
  172. */
  173. REPLACE_ALL,
  174. /**
  175. * If an added row causes a constraint violation, drop the existing row. This is the default
  176. * behavior. Depends on the existence of the _id column to work correctly.
  177. */
  178. REPLACE_EXISTING,
  179. /**
  180. * If an added row causes a constraint violation, it is ignored and the existing row is
  181. * preserved.
  182. */
  183. REPLACE_NONE
  184. }
  185. /**
  186. * Convenience function to write a complete database as Xml to a Writer
  187. * @param db
  188. * @param output
  189. * @throws SAXException Problem with Xml serialization
  190. * @throws IOException Problem interacting with output
  191. */
  192. public static void exportDbAsXmlToStream(SQLiteDatabase db, Writer output)
  193. throws SAXException, IOException
  194. {
  195. XmlSerializer serializer = Xml.newSerializer();
  196. serializer.setOutput(output);
  197. new SqliteElement(db, "database").writeToXML(new XmlSerializerHandler(serializer));
  198. serializer.flush();
  199. }
  200. /**
  201. * Convenience function to read a complete database from an XML stream
  202. * @param db Writable database
  203. * @param input Character stream for XML representation of database as written by SqliteElement
  204. * @param replace How to handle existing rows with same id's
  205. * @throws SAXException Problem interpreting XML stream
  206. * @throws IOException Problem interacting with reader
  207. */
  208. public static void importXmlStreamToDb(SQLiteDatabase db, Reader input, ReplaceStrategy replace)
  209. throws SAXException, IOException
  210. {
  211. SqliteElement element = new SqliteElement(db, "database");
  212. element.setReplaceStrategy(replace);
  213. StackContentHandler handler = new StackContentHandler();
  214. handler.pushHandlerStack(element.readFromXML(handler));
  215. Xml.parse(input, handler);
  216. }
  217. /**
  218. *
  219. * @param db Database to save/load
  220. * @param dbName Name of XML element containing entire database
  221. */
  222. public SqliteElement(SQLiteDatabase db, String dbName)
  223. {
  224. _db = db;
  225. _tableNames = new ArrayList<String>();
  226. _databaseTag = dbName;
  227. _replaceStrategy = ReplaceStrategy.REPLACE_EXISTING;
  228. }
  229. public ReplaceStrategy getReplaceStrategy()
  230. {
  231. return _replaceStrategy;
  232. }
  233. public void setReplaceStrategy(ReplaceStrategy newStrategy)
  234. {
  235. _replaceStrategy = newStrategy;
  236. }
  237. public void addTable(String tableName)
  238. {
  239. if (! _tableNames.contains(tableName))
  240. {
  241. _tableNames.add(tableName);
  242. }
  243. }
  244. public void removeTable(String tableName)
  245. {
  246. getTableNames().remove(tableName);
  247. }
  248. ArrayList<String> getTableNames()
  249. {
  250. if (_tableNames.size() == 0)
  251. {
  252. Cursor c = _db.query("sqlite_master", TABLE_ARRAY, "type = 'table'", null, null, null, null);
  253. try
  254. {
  255. while (c.moveToNext())
  256. {
  257. String t = c.getString(0);
  258. String test = t.toLowerCase();
  259. if (! test.equals("android_metadata") && ! test.equals("sqlite_sequence"))
  260. {
  261. _tableNames.add(t);
  262. }
  263. }
  264. }
  265. finally
  266. {
  267. c.close();
  268. }
  269. }
  270. return _tableNames;
  271. }
  272. /* (non-Javadoc)
  273. * @see com.antlersoft.util.xml.IElement#getElementTag()
  274. */
  275. @Override
  276. public String getElementTag() {
  277. return _databaseTag;
  278. }
  279. /* (non-Javadoc)
  280. * @see com.antlersoft.util.xml.IElement#readFromXML(com.antlersoft.util.xml.IHandlerStack)
  281. */
  282. @Override
  283. public DefaultHandler readFromXML(IHandlerStack handlerStack) {
  284. return new SqliteElementHandler(handlerStack);
  285. }
  286. /* (non-Javadoc)
  287. * @see com.antlersoft.util.xml.IElement#writeToXML(org.xml.sax.ContentHandler)
  288. */
  289. @Override
  290. public void writeToXML(ContentHandler xmlWriter) throws SAXException {
  291. xmlWriter.startElement("", "", getElementTag(), null);
  292. for (String s : getTableNames())
  293. {
  294. SimpleAttributes attributes = new SimpleAttributes();
  295. attributes.addValue(TABLE_NAME_ATTRIBUTE, s);
  296. xmlWriter.startElement("", "", TABLE_ELEMENT, attributes.getAttributes());
  297. Cursor c = _db.query(s, null, null, null, null, null, null);
  298. try
  299. {
  300. if (c.moveToFirst())
  301. {
  302. ContentValues cv = new ContentValues();
  303. do
  304. {
  305. DatabaseUtils.cursorRowToContentValues(c, cv);
  306. new ContentValuesElement(cv, ROW_ELEMENT).writeToXML(xmlWriter);
  307. }
  308. while (c.moveToNext());
  309. }
  310. }
  311. finally
  312. {
  313. c.close();
  314. }
  315. xmlWriter.endElement("", "", TABLE_ELEMENT);
  316. }
  317. xmlWriter.endElement("", "", getElementTag());
  318. }
  319. /**
  320. * DefaultHandler implementation that puts recognized XML into a database
  321. *
  322. * @author Michael A. MacDonald
  323. *
  324. */
  325. class SqliteElementHandler extends DefaultHandler {
  326. private IHandlerStack _stack;
  327. private String _currentTable;
  328. private ContentValues _lastRow;
  329. private static final long INSERT_FAILED = -1L;
  330. public SqliteElementHandler(IHandlerStack handlerStack) {
  331. _stack = handlerStack;
  332. }
  333. /* (non-Javadoc)
  334. * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
  335. */
  336. @Override
  337. public void endElement(String uri, String localName, String qName)
  338. throws SAXException {
  339. saveLastRow();
  340. }
  341. /* (non-Javadoc)
  342. * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
  343. */
  344. @Override
  345. public void startElement(String uri, String localName, String qName,
  346. Attributes attributes) throws SAXException {
  347. saveLastRow();
  348. if (localName.equals(TABLE_ELEMENT))
  349. {
  350. _currentTable = attributes.getValue(TABLE_NAME_ATTRIBUTE);
  351. if (_currentTable == null)
  352. {
  353. throw new SAXException(TABLE_NAME_ATTRIBUTE + " not found in " + TABLE_ELEMENT + " element.");
  354. }
  355. if (getTableNames().contains(_currentTable))
  356. {
  357. if (getReplaceStrategy() == ReplaceStrategy.REPLACE_ALL)
  358. {
  359. _db.delete(_currentTable, null, null);
  360. }
  361. }
  362. else
  363. _currentTable = null;
  364. }
  365. else if (localName.equals(ROW_ELEMENT))
  366. {
  367. _lastRow = new ContentValues();
  368. ContentValuesElement rowElement = new ContentValuesElement(_lastRow, ROW_ELEMENT);
  369. _stack.startWithHandler(rowElement.readFromXML(_stack), uri, localName, qName, attributes);
  370. }
  371. }
  372. private void saveLastRow()
  373. {
  374. if (_lastRow != null)
  375. {
  376. try
  377. {
  378. // See if it is a table we care about
  379. if (_currentTable != null)
  380. {
  381. long id = _db.insert(_currentTable, null, _lastRow);
  382. if (id == INSERT_FAILED)
  383. {
  384. switch (getReplaceStrategy())
  385. {
  386. case REPLACE_ALL :
  387. throw new SQLException("Failed to insert row in "+_currentTable+" after emptying");
  388. case REPLACE_EXISTING :
  389. _db.delete(_currentTable, "_id = ?", new String[] { _lastRow.getAsString("_id") });
  390. _db.insertOrThrow(_currentTable, null, _lastRow);
  391. break;
  392. }
  393. }
  394. }
  395. }
  396. finally
  397. {
  398. _lastRow = null;
  399. }
  400. }
  401. }
  402. }
  403. /**
  404. * Copies data to an XmlSerializer; not complete; only implemented enough to work with
  405. * SqliteElement
  406. * @author Michael A. MacDonald
  407. *
  408. */
  409. static class XmlSerializerHandler extends DefaultHandler {
  410. XmlSerializer _serializer;
  411. boolean _first;
  412. XmlSerializerHandler(XmlSerializer serializer)
  413. {
  414. _serializer = serializer;
  415. _first = true;
  416. }
  417. /* (non-Javadoc)
  418. * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int)
  419. */
  420. @Override
  421. public void characters(char[] ch, int start, int length)
  422. throws SAXException {
  423. try
  424. {
  425. _serializer.text(ch, start, length);
  426. }
  427. catch (IOException ioe)
  428. {
  429. throw new SAXException(ioe.getMessage(), ioe);
  430. }
  431. }
  432. /* (non-Javadoc)
  433. * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
  434. */
  435. @Override
  436. public void endElement(String uri, String localName, String qName)
  437. throws SAXException {
  438. try
  439. {
  440. _serializer.endTag(uri, qName);
  441. }
  442. catch (IOException ioe)
  443. {
  444. throw new SAXException(ioe.getMessage(), ioe);
  445. }
  446. }
  447. /* (non-Javadoc)
  448. * @see org.xml.sax.helpers.DefaultHandler#ignorableWhitespace(char[], int, int)
  449. */
  450. @Override
  451. public void ignorableWhitespace(char[] ch, int start, int length)
  452. throws SAXException {
  453. try
  454. {
  455. _serializer.ignorableWhitespace(new String(ch, start, length));
  456. }
  457. catch (IOException ioe)
  458. {
  459. throw new SAXException(ioe.getMessage(), ioe);
  460. }
  461. }
  462. /* (non-Javadoc)
  463. * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes)
  464. */
  465. @Override
  466. public void startElement(String uri, String localName, String qName,
  467. Attributes attributes) throws SAXException {
  468. try
  469. {
  470. if (_first)
  471. _first = false;
  472. else
  473. _serializer.ignorableWhitespace("\r\n");
  474. _serializer.startTag(uri, qName);
  475. if (attributes != null)
  476. {
  477. int l = attributes.getLength();
  478. for (int i = 0; i < l; i++)
  479. {
  480. _serializer.attribute(attributes.getURI(i), attributes.getQName(i), attributes.getValue(i));
  481. }
  482. }
  483. }
  484. catch (IOException ioe)
  485. {
  486. throw new SAXException(ioe.getMessage(), ioe);
  487. }
  488. }
  489. }
  490. }