Kotlin migration is done!

There will of course be refactoring and whatnot, but I consider this an accomplishment.
This commit is contained in:
Bradlee Speice 2014-11-12 23:41:05 -05:00
parent 0e7680ca9e
commit 187a73cf92
9 changed files with 113 additions and 204 deletions

View File

@ -2,7 +2,8 @@ package org.bspeice.minimalbible.activity.viewer;
import android.util.Log; import android.util.Log;
import org.bspeice.minimalbible.service.book.VerseLookupModules; import org.bspeice.minimalbible.service.lookup.DefaultVerseLookup;
import org.bspeice.minimalbible.service.lookup.VerseLookup;
import org.bspeice.minimalbible.service.manager.BookManager; import org.bspeice.minimalbible.service.manager.BookManager;
import org.crosswire.jsword.book.Book; import org.crosswire.jsword.book.Book;
@ -26,8 +27,7 @@ import rx.functions.Func1;
BibleViewer.class, BibleViewer.class,
BookFragment.class, BookFragment.class,
BookChapterNavFragment.class BookChapterNavFragment.class
}, }
includes = VerseLookupModules.class
) )
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class BibleViewerModules { public class BibleViewerModules {
@ -96,4 +96,10 @@ public class BibleViewerModules {
BookManager bookManager() { BookManager bookManager() {
return new BookManager(); return new BookManager();
} }
@Provides
@Singleton
VerseLookup verseLookup(@Named("MainBook") Book b) {
return new DefaultVerseLookup(b);
}
} }

View File

@ -12,7 +12,7 @@ import android.webkit.WebView;
import org.bspeice.minimalbible.Injector; import org.bspeice.minimalbible.Injector;
import org.bspeice.minimalbible.R; import org.bspeice.minimalbible.R;
import org.bspeice.minimalbible.activity.BaseFragment; import org.bspeice.minimalbible.activity.BaseFragment;
import org.bspeice.minimalbible.service.book.VerseLookupService; import org.bspeice.minimalbible.service.lookup.VerseLookup;
import org.crosswire.jsword.book.Book; import org.crosswire.jsword.book.Book;
import javax.inject.Inject; import javax.inject.Inject;
@ -20,7 +20,6 @@ import javax.inject.Named;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import butterknife.InjectView; import butterknife.InjectView;
import dagger.Lazy;
import rx.android.schedulers.AndroidSchedulers; import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1; import rx.functions.Action1;
import rx.subjects.PublishSubject; import rx.subjects.PublishSubject;
@ -35,18 +34,15 @@ public class BookFragment extends BaseFragment {
Injector i; Injector i;
@Inject @Inject
@Named("MainBook") @Named("MainBook")
Lazy<Book> mBook; Book mBook;
@Inject
VerseLookup verseLookup;
@InjectView(R.id.book_content) @InjectView(R.id.book_content)
WebView mainContent; WebView mainContent;
PublishSubject<String> titleReceiver = PublishSubject.create(); PublishSubject<String> titleReceiver = PublishSubject.create();
public BookFragment() {
// We can't initialize the lookupService here since the fragment hasn't been tied
// to the parent activity yet.
}
/** /**
* Returns a new instance of this fragment for the given book. * Returns a new instance of this fragment for the given book.
*/ */
@ -76,7 +72,7 @@ public class BookFragment extends BaseFragment {
// TODO: Load initial text from SharedPreferences, rather than getting the actual book. // TODO: Load initial text from SharedPreferences, rather than getting the actual book.
displayBook(mBook.get()); displayBook(mBook);
return rootView; return rootView;
} }
@ -103,8 +99,7 @@ public class BookFragment extends BaseFragment {
((BibleViewer)getActivity()).setActionBarTitle(b.getInitials()); ((BibleViewer)getActivity()).setActionBarTitle(b.getInitials());
mainContent.loadUrl(getString(R.string.book_html)); mainContent.loadUrl(getString(R.string.book_html));
VerseLookupService lookupService = new VerseLookupService(i, mBook.get()); BibleViewClient client = new BibleViewClient(b, verseLookup, titleReceiver);
BibleViewClient client = new BibleViewClient(b, lookupService, titleReceiver);
titleReceiver titleReceiver
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<String>() { .subscribe(new Action1<String>() {

View File

@ -1,7 +0,0 @@
package org.bspeice.minimalbible.exception;
/**
* Error to be thrown when no books are currently installed.
*/
public class NoBooksInstalledException extends Exception {
}

View File

@ -1,25 +0,0 @@
package org.bspeice.minimalbible.service.book;
import android.support.v4.util.LruCache;
import dagger.Module;
import dagger.Provides;
/**
* Created by bspeice on 9/1/14.
*/
@Module(injects = VerseLookupService.class)
public class VerseLookupModules {
private static final int MAX_SIZE = 1000000; // 1MB
/**
* Create a new LruCache. We're free to create new ones since they're all backed by the file
* system anyways.
*
* @return The LruCache to use
*/
@Provides
LruCache<String, String> getLruCache() {
return new LruCache<String, String>(MAX_SIZE);
}
}

View File

@ -1,148 +0,0 @@
package org.bspeice.minimalbible.service.book;
import android.support.v4.util.LruCache;
import org.bspeice.minimalbible.Injector;
import org.bspeice.minimalbible.service.format.osisparser.OsisParser;
import org.crosswire.common.xml.SAXEventProvider;
import org.crosswire.jsword.book.Book;
import org.crosswire.jsword.book.BookData;
import org.crosswire.jsword.book.BookException;
import org.crosswire.jsword.passage.Verse;
import org.xml.sax.SAXException;
import javax.inject.Inject;
import rx.functions.Action1;
import rx.schedulers.Schedulers;
import rx.subjects.PublishSubject;
/**
* This class has a simple purpose, but implements the dirty work needed to make it happen.
* The idea is this: someone wants the text for a verse, we look it up quickly.
* This means aggressive caching, cache prediction, and classes letting us know that some verses
* may be needed in the future (i.e. searching).
* <p/>
* There is one VerseLookupService per Book, but multiple VerseLookupServices can work with
* the same book. Because the actual caching mechanism is disk-based, we're safe.
* <p/>
* TODO: Statistics on cache hits/misses vs. verses cached
*/
public class VerseLookupService implements Action1<Verse> {
Book book;
@Inject
LruCache<String, String> cache;
/**
* The listener is responsible for delegating calls to cache verses.
* This way, @notifyVerse can just tell the listener what's what,
* and the listener can delegate to another thread.
*/
private PublishSubject<Verse> listener = PublishSubject.create();
public VerseLookupService(Injector i, Book b) {
listener.subscribeOn(Schedulers.io())
.subscribe(this);
this.book = b;
i.inject(this);
}
/**
* Get the text for a corresponding verse
* First, check the cache. If that doesn't exist, manually get the verse.
* In all cases, notify that we're looking up a verse so we can get the surrounding ones.
*
* @param v The verse to look up
* @return The JSON object for this verse (\<p\/>)
*/
public String getJsonVerse(Verse v) {
if (contains(v)) {
return cache.get(getEntryName(v));
} else {
// The awkward method calls below are so notifyVerse doesn't
// call the same doVerseLookup
String verseContent = doVerseLookup(v);
notifyVerse(v);
return verseContent;
}
}
/**
* Perform the ugly work of getting the actual data for a verse
* Note that we build the verse object, JS should be left to determine how
* it is displayed.
*
* @param v The verse to look up
* @return The JSON content of this verse
*/
public String doVerseLookup(Verse v) {
BookData bookData = new BookData(book, v);
try {
SAXEventProvider provider = bookData.getSAXEventProvider();
OsisParser handler = new OsisParser();
handler.setVerse(v);
provider.provideSAXEvents(handler);
return handler.getVerseContent().toJson();
} catch (BookException e) {
e.printStackTrace();
return "Unable to locate " + v.toString() + "!";
} catch (SAXException e) {
e.printStackTrace();
}
return null;
}
/**
* Not necessary, but helpful if you let us know ahead of time we should pre-cache a verse.
* For example, if something showed up in search results, it'd be helpful to start
* looking up some of the results.
*
* @param v The verse we should pre-cache
*/
public void notifyVerse(Verse v) {
listener.onNext(v);
}
/**
* Let someone know if the cache contains a verse we want
* Also provides a nice wrapper if the underlying cache isn't working properly.
*
* @param v The verse to check
* @return Whether we can retrieve the verse from our cache
*/
public boolean contains(Verse v) {
return cache.get(getEntryName(v)) != null;
}
/**
* Given a verse, what should it's name in the cache be?
* Example: Matthew 7:7 becomes:
* MAT_7_7
*
* @param v The verse we need to generate a name for
* @return The name this verse should have in the cache
*/
private String getEntryName(Verse v) {
return v.getBook().toString() + "_" +
v.getChapter() + "_" +
v.getVerse();
}
/*------------------------------------------------------------------------
IO Thread operations below
------------------------------------------------------------------------*/
/**
* The listener has let us know that we need to look up a verse. So, look up
* that one first, and get its surrounding verses as well just in case.
* We can safely assume we are not on the main thread.
*
* @param verse The verse we need to look up
*/
@Override
public void call(Verse verse) {
}
}

View File

@ -1,35 +1,34 @@
package org.bspeice.minimalbible.activity.viewer package org.bspeice.minimalbible.activity.viewer
import org.crosswire.jsword.passage.Verse import org.crosswire.jsword.passage.Verse
import org.bspeice.minimalbible.service.book.VerseLookupService
import android.webkit.WebViewClient import android.webkit.WebViewClient
import android.webkit.JavascriptInterface import android.webkit.JavascriptInterface
import org.crosswire.jsword.book.Book import org.crosswire.jsword.book.Book
import java.util.ArrayList
import android.util.Log import android.util.Log
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import org.crosswire.jsword.book.getVersification import org.crosswire.jsword.book.getVersification
import org.bspeice.minimalbible.service.lookup.VerseLookup
/** /**
* Created by bspeice on 9/14/14. * Created by bspeice on 9/14/14.
*/ */
class BibleViewClient(val b: Book, val lookup: VerseLookupService, class BibleViewClient(val b: Book, val lookup: VerseLookup,
val subject: PublishSubject<String>?) : WebViewClient() { val subject: PublishSubject<String>?) : WebViewClient() {
// We can receive and return only primitives and Strings. Still means we can use JSON :) // We can receive and return only primitives and Strings. Still means we can use JSON :)
JavascriptInterface fun getVerse(ordinal: Int): String { JavascriptInterface fun getVerse(ordinal: Int): String {
val v = Verse(b.getVersification(), ordinal) val v = Verse(b.getVersification(), ordinal)
// TODO: WebView should notify us what verse it's on // TODO: WebView should notify us what verse it's on
subject?.onNext(v.getBook().toString() + " " + v.getChapter() + ":" + v.getVerse()) subject?.onNext("${v.getBook()} ${v.getChapter()}:${v.getVerse()}")
return lookup.getJsonVerse(v) return lookup getJson v
} }
JavascriptInterface fun getVerses(first: Int, count: Int): String { JavascriptInterface fun getVerses(first: Int, count: Int): String {
Log.e("getVerses", "First: " + first + " count: " + count) Log.e("getVerses", "First: $first count: $count")
val verses: MutableList<String> = ArrayList<String>() val verses: MutableList<String> = linkedListOf()
var trueCount: Int val trueCount: Int
var trueFirst: Int val trueFirst: Int
when { when {
first < 0 - count -> return "" first < 0 - count -> return ""
first < 0 -> { first < 0 -> {
@ -45,7 +44,7 @@ class BibleViewClient(val b: Book, val lookup: VerseLookupService,
for (i in trueFirst..trueFirst + trueCount - 1) { for (i in trueFirst..trueFirst + trueCount - 1) {
verses.add(getVerse(i)) verses.add(getVerse(i))
} }
Log.e("getVerses", "return verses size: " + verses.size.toString()) Log.e("getVerses", "return verses size: ${verses.size}")
return verses.toString() return verses.toString()
} }
} }

View File

@ -0,0 +1,3 @@
package org.bspeice.minimalbible.exception
class NoBooksInstalledException() : Exception("No books currently installed!") {}

View File

@ -22,6 +22,8 @@ class OsisParser() : DefaultHandler() {
// TODO: Implement a stack to keep min API 8 // TODO: Implement a stack to keep min API 8
val doWrite = ArrayDeque<Boolean>() val doWrite = ArrayDeque<Boolean>()
fun getJson() = verseContent.toJson()
override fun startElement(uri: String, localName: String, override fun startElement(uri: String, localName: String,
qName: String, attributes: Attributes) { qName: String, attributes: Attributes) {
when (localName) { when (localName) {

View File

@ -0,0 +1,84 @@
package org.bspeice.minimalbible.service.lookup
import org.crosswire.jsword.book.Book
import android.support.v4.util.LruCache
import rx.functions.Action1
import org.crosswire.jsword.passage.Verse
import rx.subjects.PublishSubject
import rx.schedulers.Schedulers
import org.crosswire.jsword.book.BookData
import org.bspeice.minimalbible.service.format.osisparser.OsisParser
/**
* Created by bspeice on 11/12/14.
*/
open class VerseLookup(val b: Book,
val cache: LruCache<Int, String> = LruCache(1000000)) : Action1<Verse> {
/**
* The listener servers to let other objects notify us we should pre-cache verses
*/
val listener: PublishSubject<Verse> = PublishSubject.create();
{
listener.observeOn(Schedulers.io())
.subscribe(this)
}
fun getVerseId(v: Verse) = v.getOrdinal()
fun getJson(v: Verse): String =
if (contains(v))
cache[getVerseId(v)]
else {
val content = doLookup(v)
notify(v)
content
}
/**
* Perform the ugly work of getting the actual data for a verse
* Note that we build the verse object, JS should be left to determine how
* it is displayed.
*
* @param v The verse to look up
* @return The JSON content of this verse
*/
fun doLookup(v: Verse): String {
val data = BookData(b, v)
val provider = data.getSAXEventProvider()
val handler = OsisParser()
handler.verse = v
provider provideSAXEvents handler
return handler.getJson()
}
/**
* Not necessary, but helpful if you let us know ahead of time we should pre-cache a verse.
* For example, if something showed up in search results, it'd be helpful to start
* looking up some of the results.
*
* @param v The verse we should pre-cache
*/
fun notify(v: Verse) = listener onNext v
/**
* Let someone know if the cache contains a verse we want
* Also provides a nice wrapper if the underlying cache isn't working properly.
*
* @param v The verse to check
* @return Whether we can retrieve the verse from our cache
*/
fun contains(v: Verse) = cache[v.getOrdinal()] != null
// IO Thread operations begin here
/**
* Someone was nice enough to let us know that a verse was recently called,
* we should probably cache its neighbors!
*/
override fun call(t1: Verse?) {
}
}
class DefaultVerseLookup(b: Book) : VerseLookup(b) {}