/**************************************************************************************** * Copyright (c) 2009 Daniel Svärd <daniel.svard@gmail.com> * * * * This program 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. * * * * This program 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 * * this program. If not, see <http://www.gnu.org/licenses/>. * ****************************************************************************************/ package com.ichi2.anki.model; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; import java.util.TreeSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.ichi2.anki.Utils; import com.ichi2.anki.db.AnkiDb; /** * Anki fact. * A fact is a single piece of information, made up of a number of fields. * See http://ichi2.net/anki/wiki/KeyTermsAndConcepts#Facts */ public class Fact { // TODO: Javadoc. // TODO: Finish porting from facts.py. // TODO: Methods to read/write from/to DB. private Logger log = LoggerFactory.getLogger(Fact.class); private long mId; private long mModelId; // private double mCreated; // private double mModified; private String mTags; private String mSpaceUntil; // Once obsolete, under libanki1.1 spaceUntil is reused as a html-stripped cache of the fields // private Model mModel; private TreeSet<Field> mFields; private Deck mDeck; // Generate fact object from its ID public Fact(Deck deck, long id) { mDeck = deck; fromDb(id); // TODO: load fields associated with this fact. } public Fact(Deck deck, Model model) { mDeck = deck; // mModel = model; mId = Utils.genID(); if (model == null) { mModelId = deck.getCurrentModelId(); } else { mModelId = model.getId(); } TreeMap<Long, FieldModel> mFieldModels = new TreeMap<Long, FieldModel>(); FieldModel.fromDb(deck, mModelId, mFieldModels); mFields = new TreeSet<Field>(new FieldOrdinalComparator()); for (Entry<Long, FieldModel> entry : mFieldModels.entrySet()) { mFields.add(new Field(mId, entry.getValue())); } } /** * @return the mId */ public long getId() { return mId; } /** * @return the mTags */ public String getTags() { return mTags; } /** * @param tags the tags to set */ public void setTags(String tags) { mTags = tags; } /** * @return the mModelId */ public long getModelId() { return mModelId; } /** * @return the fields */ public TreeSet<Field> getFields() { return mFields; } private boolean fromDb(long id) { mId = id; AnkiDb ankiDB = mDeck.getDB(); ResultSet result = null; try { result = ankiDB.rawQuery("SELECT id, modelId, created, modified, tags, spaceUntil " + "FROM facts " + "WHERE id = " + id); if (!result.next()) { log.warn("Fact.java (constructor): No result from query."); return false; } mId = result.getLong(1); mModelId = result.getLong(2); // mCreated = cursor.getDouble(3); // mModified = cursor.getDouble(4); mTags = result.getString(5); mSpaceUntil = result.getString(6); } catch (SQLException e) { e.printStackTrace(); } finally { if (result != null) { try { result.close(); } catch (SQLException e) { } } } ResultSet fieldsResult = null; try { fieldsResult = ankiDB.rawQuery("SELECT id, factId, fieldModelId, value " + "FROM fields " + "WHERE factId = " + id); mFields = new TreeSet<Field>(new FieldOrdinalComparator()); while (fieldsResult.next()) { long fieldId = fieldsResult.getLong(1); long fieldModelId = fieldsResult.getLong(3); String fieldValue = fieldsResult.getString(4); ResultSet fieldModelResult = null; FieldModel currentFieldModel = null; try { // Get the field model for this field fieldModelResult = ankiDB.rawQuery("SELECT id, ordinal, modelId, name, description " + "FROM fieldModels " + "WHERE id = " + fieldModelId); fieldModelResult.first(); currentFieldModel = new FieldModel(fieldModelResult.getLong(1), fieldModelResult.getInt(2), fieldModelResult.getLong(3), fieldModelResult.getString(4), fieldModelResult.getString(5)); } finally { if (fieldModelResult != null) { fieldModelResult.close(); } } mFields.add(new Field(fieldId, id, currentFieldModel, fieldValue)); } } catch (SQLException e) { e.printStackTrace(); } finally { if (fieldsResult != null) { try { fieldsResult.close(); } catch (SQLException e) { } } } // Read Fields return true; } public void toDb() { double now = Utils.now(); // update facts table Map<String, Object> updateValues = new HashMap<String, Object>(); updateValues.put("modified", now); updateValues.put("tags", mTags); updateValues.put("spaceUntil", mSpaceUntil); mDeck.getDB().update(mDeck, "facts", updateValues, "id = " + mId); // update fields table for (Field f : mFields) { updateValues = new HashMap<String, Object>(); updateValues.put("value", f.mValue); mDeck.getDB().update(mDeck, "fields", updateValues, "id = " + f.mFieldId); } } public String getFieldValue(String fieldModelName) { for (Field f : mFields) { if (f.mFieldModel.getName().equals(fieldModelName)) { return f.mValue; } } return null; } public long getFieldModelId(String fieldModelName) { for (Field f : mFields) { if (f.mFieldModel.getName().equals(fieldModelName)) { return f.mFieldModel.getId(); } } return 0; } public LinkedList<Card> getUpdatedRelatedCards() { // TODO return instances of each card that is related to this fact LinkedList<Card> returnList = new LinkedList<Card>(); ResultSet result = mDeck.getDB(). rawQuery("SELECT id, factId FROM cards " + "WHERE factId = " + mId); try { while (result.next()) { Card newCard = new Card(mDeck); newCard.fromDB(result.getLong(1)); newCard.loadTags(); HashMap<String, String> newQA = CardModel.formatQA(this, newCard.getCardModel(), newCard.splitTags()); newCard.setQuestion(newQA.get("question")); newCard.setAnswer(newQA.get("answer")); returnList.add(newCard); } } catch (SQLException e) { e.printStackTrace(); } finally { if (result != null) { try { result.close(); } catch (SQLException e) { } } } return returnList; } public void setModified() { setModified(false, null, true); } public void setModified(boolean textChanged) { setModified(textChanged, null, true); } public void setModified(boolean textChanged, Deck deck) { setModified(textChanged, deck, true); } public void setModified(boolean textChanged, Deck deck, boolean media) { // mModified = Utils.now(); if (textChanged) { assert (deck != null); mSpaceUntil = ""; StringBuilder str = new StringBuilder(1024); for (Field f : getFields()) { str.append(f.getValue()).append(" "); } mSpaceUntil = str.toString(); mSpaceUntil.substring(0, mSpaceUntil.length() - 1); mSpaceUntil = Utils.stripHTMLMedia(mSpaceUntil); log.debug("spaceUntil = " + mSpaceUntil); for (Card card : getUpdatedRelatedCards()) { card.setModified(); card.toDB(); // card.rebuildQA(deck); } } } public static final class FieldOrdinalComparator implements Comparator<Field> { public int compare(Field object1, Field object2) { return object1.mOrdinal - object2.mOrdinal; } } public class Field { // TODO: Javadoc. // Methods for reading/writing from/to DB. // BEGIN SQL table entries private long mFieldId; // Primary key id, but named fieldId to no hide Fact.id private long mFactId; // Foreign key facts.id private long mFieldModelId; // Foreign key fieldModel.id private int mOrdinal; private String mValue; // END SQL table entries // BEGIN JOINed entries private FieldModel mFieldModel; // END JOINed entries // Backward reference // private Fact mFact; // for creating instances of existing fields public Field(long id, long factId, FieldModel fieldModel, String value) { mFieldId = id; mFactId = factId; mFieldModelId = fieldModel.getId(); mValue = value; mFieldModel = fieldModel; mOrdinal = fieldModel.getOrdinal(); } // For creating new fields public Field(long factId, FieldModel fieldModel) { if (fieldModel != null) { mFieldModel = fieldModel; mOrdinal = fieldModel.getOrdinal(); } mFactId = factId; mFieldModelId = fieldModel.getId(); mValue = ""; mFieldId = Utils.genID(); } /** * @return the FactId */ public long getFactId() { return mFactId; } /** * @return the FieldModelId */ public long getFieldModelId() { return mFieldModelId; } /** * @return the Ordinal */ public int getOrdinal() { return mOrdinal; } /** * @param value the value to set */ public void setValue(String value) { mValue = value; } /** * @return the value */ public String getValue() { return mValue; } /** * @return the Field's Id */ public long getId() { return mFieldId; } /** * @return the fieldModel */ public FieldModel getFieldModel() { return mFieldModel; } } }