/*
* @copyright 2012 Philip Warner
* @license GNU General Public License V3
*
* 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.booklist;
import com.eleybourn.bookcatalogue.BookCatalogueApp;
import com.eleybourn.bookcatalogue.BookCataloguePreferences;
import com.eleybourn.bookcatalogue.R;
import com.eleybourn.bookcatalogue.booklist.BooklistStyle.CompoundKey;
import com.eleybourn.bookcatalogue.database.DbUtils.DomainDefinition;
import com.eleybourn.bookcatalogue.properties.BooleanListProperty;
import com.eleybourn.bookcatalogue.properties.ListProperty.ItemEntries;
import com.eleybourn.bookcatalogue.properties.Properties;
import com.eleybourn.bookcatalogue.properties.PropertyGroup;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import static com.eleybourn.bookcatalogue.booklist.BooklistGroup.RowKinds.*;
/**
* Class representing a single level in the booklist hierarchy.
*
* @author Philip Warner
*/
public class BooklistGroup implements Serializable {
private static final long serialVersionUID = 1012206875683862714L;
/**
* Static definitions of the kinds of rows that can be displayed and summarized.
* Adding new row types needs to involve changes to:
*
* - BooklistBuilder (to build the correct SQL)
* - BooksMultitypeListHandler (to know what to do with the new type)
*
* @author Philip Warner
*/
public static final class RowKinds {
public static final int ROW_KIND_BOOK = 0; // Supported
public static final int ROW_KIND_AUTHOR = 1; // Supported
public static final int ROW_KIND_SERIES = 2; // Supported
public static final int ROW_KIND_GENRE = 3; // Supported
public static final int ROW_KIND_PUBLISHER = 4; // Supported
public static final int ROW_KIND_READ_AND_UNREAD = 5; // Supported
public static final int ROW_KIND_LOANED = 6; // Supported
public static final int ROW_KIND_YEAR_PUBLISHED = 7; // Supported
public static final int ROW_KIND_MONTH_PUBLISHED = 8; // Supported
public static final int ROW_KIND_TITLE_LETTER = 9; // Supported
public static final int ROW_KIND_YEAR_ADDED = 10; // Supported
public static final int ROW_KIND_MONTH_ADDED = 11; // Supported
public static final int ROW_KIND_DAY_ADDED = 12; // Supported
public static final int ROW_KIND_FORMAT = 13; // Supported
public static final int ROW_KIND_YEAR_READ = 14; // Supported
public static final int ROW_KIND_MONTH_READ = 15; // Supported
public static final int ROW_KIND_DAY_READ = 16; // Supported
public static final int ROW_KIND_LOCATION = 17; // Supported
public static final int ROW_KIND_LANGUAGE = 18; // Supported
public static final int ROW_KIND_UPDATE_YEAR = 19; // Supported
public static final int ROW_KIND_UPDATE_MONTH = 20; // Supported
public static final int ROW_KIND_UPDATE_DAY = 21; // Supported
public static final int ROW_KIND_RATING = 22; // Supported
public static final int ROW_KIND_BOOKSHELF = 23; // Supported
// NEWKIND: Add new kinds here
public static final int ROW_KIND_MAX = 23; // **** NOTE **** ALWAYS update after adding a row kind...
}
private static final UniqueMap<Integer, String> mRowKindNames = new UniqueMap<Integer, String>();
static {
mRowKindNames.add(ROW_KIND_AUTHOR, BookCatalogueApp.getResourceString(R.string.author));
mRowKindNames.add(ROW_KIND_SERIES, BookCatalogueApp.getResourceString(R.string.series));
mRowKindNames.add(ROW_KIND_GENRE, BookCatalogueApp.getResourceString(R.string.genre));
mRowKindNames.add(ROW_KIND_PUBLISHER, BookCatalogueApp.getResourceString(R.string.publisher));
mRowKindNames.add(ROW_KIND_READ_AND_UNREAD, BookCatalogueApp.getResourceString(R.string.read_amp_unread));
mRowKindNames.add(ROW_KIND_LOANED, BookCatalogueApp.getResourceString(R.string.loaned));
mRowKindNames.add(ROW_KIND_YEAR_PUBLISHED, BookCatalogueApp.getResourceString(R.string.publication_year));
mRowKindNames.add(ROW_KIND_MONTH_PUBLISHED, BookCatalogueApp.getResourceString(R.string.publication_month));
mRowKindNames.add(ROW_KIND_TITLE_LETTER, BookCatalogueApp.getResourceString(R.string.sort_title_first_letter));
mRowKindNames.add(ROW_KIND_YEAR_ADDED, BookCatalogueApp.getResourceString(R.string.added_year));
mRowKindNames.add(ROW_KIND_MONTH_ADDED, BookCatalogueApp.getResourceString(R.string.added_month));
mRowKindNames.add(ROW_KIND_DAY_ADDED, BookCatalogueApp.getResourceString(R.string.added_day));
mRowKindNames.add(ROW_KIND_FORMAT, BookCatalogueApp.getResourceString(R.string.format));
mRowKindNames.add(ROW_KIND_YEAR_READ, BookCatalogueApp.getResourceString(R.string.read_year));
mRowKindNames.add(ROW_KIND_MONTH_READ, BookCatalogueApp.getResourceString(R.string.read_month));
mRowKindNames.add(ROW_KIND_DAY_READ, BookCatalogueApp.getResourceString(R.string.read_day));
mRowKindNames.add(ROW_KIND_LOCATION, BookCatalogueApp.getResourceString(R.string.location));
mRowKindNames.add(ROW_KIND_LANGUAGE, BookCatalogueApp.getResourceString(R.string.language));
mRowKindNames.add(ROW_KIND_UPDATE_DAY, BookCatalogueApp.getResourceString(R.string.update_day));
mRowKindNames.add(ROW_KIND_UPDATE_MONTH, BookCatalogueApp.getResourceString(R.string.update_month));
mRowKindNames.add(ROW_KIND_UPDATE_YEAR, BookCatalogueApp.getResourceString(R.string.update_year));
mRowKindNames.add(ROW_KIND_RATING, BookCatalogueApp.getResourceString(R.string.rating));
mRowKindNames.add(ROW_KIND_BOOKSHELF, BookCatalogueApp.getResourceString(R.string.bookshelf));
// NEWKIND: Add new kinds here
mRowKindNames.add(ROW_KIND_BOOK, BookCatalogueApp.getResourceString(R.string.book));
// Sanity check
for(int i = 0; i <= ROW_KIND_MAX; i++) {
if (!mRowKindNames.containsKey(i))
throw new RuntimeException("Missing row kind name for row kind " + i);
}
}
/**
* Subclass of HashMap with an add(...) method that ensures values are unique.
*
* @author pjw
*
* @param <K> Type of Key values
* @param <V> Type of data values
*/
private static class UniqueMap<K, V> extends HashMap<K, V> {
private static final long serialVersionUID = 1L;
/**
* Add a value, throwing an exception if key already stored
*
* @param key Key for new value
* @param value Data for new value
*/
public V add(K key, V value) {
if (super.put(key, value) != null)
throw new RuntimeException("Map already contains key value" + key);
return null;
}
@Override
/**
* Just calls add(...)
*
* @param key Key for new value
* @param value Data for new value
*/
public V put(K key, V value) {
return add(key, value);
}
///**
// * Same semantics as old 'put' method; just replace the value (or add).
// *
// * @param key Key for new value
// * @param value Data for new value
// *
// * @return Old value, or null
// */
//public V replace(K key, V value) {
// return super.put(key, value);
//}
}
/**
* Return a list of all defined row kinds.
*
* @return
*/
public static int[] getRowKinds() {
int[] kinds = new int[mRowKindNames.size()];
int pos = 0;
for(Entry<Integer,String> e: mRowKindNames.entrySet()) {
kinds[pos++] = e.getKey();
}
return kinds;
}
/**
* Return a list of BooklistGroups, one for each defined row kind
*
* @return
*/
public static ArrayList<BooklistGroup> getAllGroups() {
ArrayList<BooklistGroup> list = new ArrayList<BooklistGroup>();
for(Entry<Integer, String> e : mRowKindNames.entrySet()) {
final int kind = e.getKey();
if (kind != ROW_KIND_BOOK)
list.add(newGroup(kind));
}
return list;
}
/**
* Create a new BooklistGroup of the specified kind, creating any more specific subclasses as necessary.
*
* @param kind Kind of group to create
*
* @return
*/
public static BooklistGroup newGroup(int kind) {
BooklistGroup g;
switch(kind) {
case ROW_KIND_AUTHOR:
g = new BooklistGroup.BooklistAuthorGroup();
break;
case ROW_KIND_SERIES:
g = new BooklistGroup.BooklistSeriesGroup();
break;
default:
g = new BooklistGroup(kind);
break;
}
return g;
}
/**
* Specialized BooklistGroup representing an Author group. Includes extra attributes based
* on preferences.
*
* @author Philip Warner
*/
public static class BooklistSeriesGroup extends BooklistGroup /* implements Parcelable */ {
private static final long serialVersionUID = 9023218506278704155L;
/** Show book under each series it appears in? */
public transient BooleanListProperty mAllSeries;
/** mAllSeries Parameter values and descriptions */
private static ItemEntries<Boolean> mAllSeriesItems = new ItemEntries<Boolean>();
static {
String kind = BookCatalogueApp.getResourceString(R.string.series);
mAllSeriesItems.add(null, R.string.use_default_setting);
mAllSeriesItems.add(false, R.string.show_book_under_primary_thing, kind);
mAllSeriesItems.add(true, R.string.show_book_under_each_thing, kind);
}
private void initProperties() {
mAllSeries = new BooleanListProperty(mAllSeriesItems, "AllSeries", PropertyGroup.GRP_SERIES, R.string.books_in_multiple_series, null, BookCataloguePreferences.PREF_SHOW_ALL_SERIES, false);
mAllSeries.setHint(R.string.hint_series_book_may_appear_more_than_once);
}
/**
* Custom serialization support.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
// We use read/write Object so that NULL values are preserved
out.writeObject(mAllSeries.get());
}
/**
* Pseudo-constructor for custom serialization support.
* We need to set the name resource ID for the properties since these may change across versions.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
initProperties();
// We use read/write Object so that NULL values are preserved
mAllSeries.set((Boolean)in.readObject());
}
BooklistSeriesGroup() {
super(ROW_KIND_SERIES);
initProperties();
mAllSeries.set((Boolean)null);
}
public boolean getAllSeries() {
return mAllSeries.getResolvedValue();
}
@Override
public void getStyleProperties(Properties list) {
super.getStyleProperties(list);
list.add(mAllSeries);
}
}
/**
* Specialized BooklistGroup representing an Series group. Includes extra attributes based
* on preferences.
*
* @author Philip Warner
*/
public static class BooklistAuthorGroup extends BooklistGroup {
private static final long serialVersionUID = -1984868877792780113L;
/** Support for 'Show Given Name' property */
public transient BooleanListProperty mGivenName;
private static ItemEntries<Boolean> mGivenNameFirstItems = new ItemEntries<Boolean>();
static {
mGivenNameFirstItems.add(null, R.string.use_default_setting);
mGivenNameFirstItems.add(false, R.string.family_name_first_eg);
mGivenNameFirstItems.add(true, R.string.given_name_first_eg);
}
/** Support for 'Show All Authors of Book' property */
public transient BooleanListProperty mAllAuthors;
private static ItemEntries<Boolean> mAllAuthorsItems = new ItemEntries<Boolean>();
static {
String kind = BookCatalogueApp.getResourceString(R.string.author);
mAllAuthorsItems.add(null, R.string.use_default_setting);
mAllAuthorsItems.add(false, R.string.show_book_under_primary_thing, kind);
mAllAuthorsItems.add(true, R.string.show_book_under_each_thing, kind);
}
/**
* Create the properties objects; these are transient, so not created by deserialization, and need to
* be created in constructors as well.
*/
private void initProperties() {
mAllAuthors = new BooleanListProperty(mAllAuthorsItems, "AllAuthors", PropertyGroup.GRP_AUTHOR, R.string.books_with_multiple_authors, BookCataloguePreferences.PREF_SHOW_ALL_AUTHORS);
mAllAuthors.setHint(R.string.hint_authors_book_may_appear_more_than_once);
mGivenName = new BooleanListProperty(mGivenNameFirstItems, "GivenName", PropertyGroup.GRP_AUTHOR, R.string.format_of_author_names, BookCataloguePreferences.PREF_DISPLAY_FIRST_THEN_LAST_NAMES);
}
/**
* Custom serialization support.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
// We use read/write Object so that NULL values are preserved
out.writeObject(mAllAuthors.get());
out.writeObject(mGivenName.get());
}
/**
* Pseudo-constructor for custom serialization support.
* We need to set the name resource ID for the properties since these may change across versions.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
initProperties();
// We use read/write Object so that NULL values are preserved
mAllAuthors.set((Boolean)in.readObject());
mGivenName.set((Boolean)in.readObject());
}
BooklistAuthorGroup() {
super(ROW_KIND_AUTHOR);
initProperties();
mAllAuthors.set((Boolean)null);
mGivenName.set((Boolean)null);
}
/**
* Accessor
*
* @return
*/
public boolean getAllAuthors() {
return mAllAuthors.getResolvedValue();
}
/**
* Accessor
*
* @return
*/
public void setAllAuthors(Boolean allAuthors) {
mAllAuthors.set(allAuthors);
}
/**
* Accessor
*
* @return
*/
public boolean getGivenName() {
return mGivenName.getResolvedValue();
}
/**
* Accessor
*
* @return
*/
public void setGivenName(Boolean giveName) {
mGivenName.set(giveName);
}
/**
* Get the Property objects that this group will contribute to a Style.
*/
@Override
public void getStyleProperties(Properties list) {
super.getStyleProperties(list);
list.add(mAllAuthors);
list.add(mGivenName);
}
}
/** The Row Kind of this group */
public int kind;
/** The domains represented by this group. Set at runtime by builder based on current group and outer groups */
public transient ArrayList<DomainDefinition> groupDomains;
/** The domain used to display this group. Set at runtime by builder based on internal logic of builder */
public transient DomainDefinition displayDomain;
/** Compound key of this group. Set at runtime by builder based on current group and outer groups */
private transient CompoundKey mCompoundKey;
BooklistGroup(int kind) {
this.kind = kind;
}
/** Setter for compound key */
public void setKeyComponents(String prefix, DomainDefinition...domains) {
mCompoundKey = new CompoundKey(prefix, domains);
}
/** Getter for compound key */
public CompoundKey getCompoundKey() {
return mCompoundKey;
}
public String getName() {
return mRowKindNames.get(kind);
}
public void getStyleProperties(Properties list) {
}
/**
* Custom serialization support.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
}
/**
* Pseudo-constructor for custom serialization support.
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
}
}