package org.sorz.lab.smallcloudemoji.parsers; import org.sorz.lab.smallcloudemoji.db.Category; import org.sorz.lab.smallcloudemoji.db.CategoryDao; import org.sorz.lab.smallcloudemoji.db.DaoSession; import org.sorz.lab.smallcloudemoji.db.Entry; import org.sorz.lab.smallcloudemoji.db.EntryDao; import org.sorz.lab.smallcloudemoji.db.Repository; import org.sorz.lab.smallcloudemoji.db.RepositoryDao; import org.sorz.lab.smallcloudemoji.exceptions.LoadingCancelException; import org.sorz.lab.smallcloudemoji.exceptions.PullParserException; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; /** * Parse a stream into one repository, and add it into database. * The abstract class implements all database-related methods, but not parser-related methods. */ public abstract class RepositoryLoader { private final DaoSession daoSession; private final RepositoryDao repositoryDao; private final CategoryDao categoryDao; private final EntryDao entryDao; private Date updateDate; private boolean isNewRepository; private RepositoryLoaderEventListener eventListener; private Repository currentRepository; private Category currentCategory; private List<Entry> currentEntries; public void setLoaderEventListener(RepositoryLoaderEventListener eventListener) { this.eventListener = eventListener; } RepositoryLoader(DaoSession daoSession) { this.daoSession = daoSession; repositoryDao = daoSession.getRepositoryDao(); categoryDao = daoSession.getCategoryDao(); entryDao = daoSession.getEntryDao(); } public void loadToDatabase(Repository repository, final Reader reader) throws Exception { updateDate = new Date(); if (repository.getLastUpdateDate() == null) { repository.setLastUpdateDate(updateDate); isNewRepository = true; } // Insert it into database only when the repository is new one, i.e. ID is null. if (repository.getId() == null) repositoryDao.insert(repository); currentRepository = repository; daoSession.callInTx(new Callable<Void>() { @Override public Void call() throws Exception { try { loadRepository(reader); } catch (Exception e) { // Delete all things if the repository is new one. // Otherwise keep all things to prevent lost usage statistics. if (isNewRepository) { entryDao.queryBuilder() .where(EntryDao.Properties.LastUpdateDate.eq(updateDate), EntryDao.Properties.CategoryId.in( getCategoryIds(currentRepository))) .buildDelete().executeDeleteWithoutDetachingEntities(); categoryDao.queryBuilder() .where(CategoryDao.Properties.LastUpdateDate.eq(updateDate), CategoryDao.Properties.RepositoryId.eq( currentRepository.getId())) .buildDelete().executeDeleteWithoutDetachingEntities(); currentRepository.delete(); } throw e; } // If update old one successfully, delete all old things. if (!isNewRepository) { entryDao.queryBuilder() .where(EntryDao.Properties.LastUpdateDate.notEq(updateDate), EntryDao.Properties.CategoryId.in( getCategoryIds(currentRepository))) .buildDelete().executeDeleteWithoutDetachingEntities(); categoryDao.queryBuilder() .where(CategoryDao.Properties.LastUpdateDate.notEq(updateDate), CategoryDao.Properties.RepositoryId.eq( currentRepository.getId())) .buildDelete().executeDeleteWithoutDetachingEntities(); } return null; } }); // Update updateDate after all, because it will be used to determine whether it is new one. repository.setLastUpdateDate(updateDate); repository.update(); } /** * Add a category to database, * and all entries will be added to this category before endCategory called. * Must be called before endCategory() called. * * @param name The name of category. * @throws LoadingCancelException Loading canceled via LoaderEventListener. */ void beginCategory(String name) throws LoadingCancelException { if (currentCategory != null) endCategory(); Category category = null; // Try to get category from database first // if the repository which it belong to is not new added one. if (!isNewRepository) { category = categoryDao.queryBuilder() .where(CategoryDao.Properties.RepositoryId.eq(currentRepository.getId()), CategoryDao.Properties.Name.eq(name)) .unique(); } if (category == null) { category = new Category(null, name, false, updateDate, null); category.setRepository(currentRepository); categoryDao.insert(category); } if (eventListener != null) if (eventListener.onLoadingCategory(category)) throw new LoadingCancelException(); currentCategory = category; if (currentEntries != null) { for (Entry entry : currentEntries) entry.setCategory(currentCategory); } else { currentEntries = new ArrayList<Entry>(); } } /** * Add a entry to buffer. * * @param emoticon Emoticon string. * @param description Optional description string. * @throws LoadingCancelException Loading canceled via LoaderEventListener. */ void addEntry(String emoticon, String description) throws LoadingCancelException { if (description == null) description = ""; if (currentEntries == null) currentEntries = new ArrayList<Entry>(); // Category will be set later when beginCategory() called if it's unknown now. Long categoryId = currentCategory == null ? null : currentCategory.getId(); Entry entry = new Entry(null, emoticon, description, false, null, updateDate, categoryId); currentEntries.add(entry); if (eventListener != null) if (eventListener.onEntryLoaded(entry)) throw new LoadingCancelException(); } /** * Add or update all entries on buffer into database. * Must be called in the end of parsing category. */ void endCategory() { List<Entry> updateEntries = new ArrayList<Entry>(); List<Entry> insertEntries = new ArrayList<Entry>(); if (!isNewRepository) { List<Entry> oldEntries = entryDao.queryBuilder() .where(EntryDao.Properties.CategoryId.eq(currentCategory.getId())) .list(); if (oldEntries.size() > 0) { Map<String, Entry> oldEntryMap = new HashMap<String, Entry>(oldEntries.size()); for (Entry entry : oldEntries) { oldEntryMap.put(entry.getEmoticon(), entry); } for (Entry entry : currentEntries) { Entry oldEntry = oldEntryMap.get(entry.getEmoticon()); if (oldEntry != null) { oldEntry.setLastUpdateDate(updateDate); oldEntry.setDescription(entry.getDescription()); updateEntries.add(oldEntry); } else { insertEntries.add(entry); } } } else { insertEntries = currentEntries; } } else { insertEntries = currentEntries; } entryDao.insertInTx(insertEntries); entryDao.updateInTx(updateEntries); if (!currentCategory.getLastUpdateDate().equals(updateDate)) { currentCategory.setLastUpdateDate(updateDate); currentCategory.update(); } currentCategory = null; currentEntries = null; } protected abstract void loadRepository(Reader reader) throws PullParserException, IOException, LoadingCancelException; private List<Long> getCategoryIds(Repository repository) { List<Category> categories = repository.getCategories(); List<Long> categoryIds = new ArrayList<Long>(categories.size()); for (Category category : categories) categoryIds.add(category.getId()); return categoryIds; } }