/*
* This file is part of the Illarion project.
*
* Copyright © 2015 - Illarion e.V.
*
* Illarion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Illarion 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.
*/
package illarion.client.resources;
import illarion.client.util.IdWrapper;
import illarion.common.data.Book;
import org.jetbrains.annotations.Contract;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
/**
* The book factory stores the references to the books.
*
* @author Martin Karing <nitram@illarion.org>
*/
public final class BookFactory implements ResourceFactory<IdWrapper<String>> {
/**
* The logger instance of this class.
*/
@Nonnull
private static final Logger log = LoggerFactory.getLogger(BookFactory.class);
/**
* The map that stores the file names in relation to the book IDs.
*/
@Nonnull
private final Map<Integer, String> fileMap;
/**
* The map that stores the book data in relation to the book IDs.
*/
@Nonnull
private final Map<Integer, Reference<Book>> bookMap;
/**
* The singleton instance of this factory.
*/
@Nonnull
private static final BookFactory INSTANCE = new BookFactory();
/**
* Get the singleton instance of this factory.
*
* @return the singleton instance of this factory
*/
@Nonnull
@Contract(pure = true)
public static BookFactory getInstance() {
return INSTANCE;
}
/**
* Default constructor.
*/
private BookFactory() {
fileMap = new HashMap<>();
bookMap = new HashMap<>();
}
@Override
public void init() {
// nothing to do
}
@Override
public void loadingFinished() {
// nothing
}
/**
* Store a resource in this factory.
*
* @param resource the resource to store
*/
@Override
public void storeResource(@Nonnull IdWrapper<String> resource) {
if (getBookUrl(resource.getObject()) == null) {
log.error("Book ID: {} not found. File {}.book.xml is missing in the resources.",
Integer.toString(resource.getId()), resource.getObject());
} else {
fileMap.put(resource.getId(), resource.getObject());
}
}
/**
* Get the URL of a book with a specific ID.
*
* @param id the ID of the book
* @return the URL to the book resource
*/
@Nullable
@Contract(pure = true)
private URL getBookUrl(int id) {
String bookRef = fileMap.get(id);
if (bookRef == null) {
return null;
}
return getBookUrl(bookRef);
}
/**
* Get the URL of a book with a specific base name.
*
* @param baseName the base name of the book
* @return the URL to the book resource
*/
@Nullable
@Contract(pure = true)
private static URL getBookUrl(@Nonnull String baseName) {
return Thread.currentThread().getContextClassLoader().getResource("books/" + baseName + ".book.xml");
}
/**
* Fetch a book from this factory.
*
* @param id the ID of the book
* @return the book with all its data
*/
@Nullable
@Contract(pure = true)
public Book getBook(int id) {
Reference<Book> bookReference = bookMap.get(id);
Book requestedBook = null;
if (bookReference != null) {
requestedBook = bookReference.get();
}
if (requestedBook == null) {
URL bookUrl = getBookUrl(id);
if (bookUrl == null) {
log.error("Book resource not found: {}", Integer.toString(id));
return null;
}
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setIgnoringComments(true);
docBuilderFactory.setIgnoringElementContentWhitespace(true);
docBuilderFactory.setNamespaceAware(true);
docBuilderFactory.setValidating(false);
try {
Document document = docBuilderFactory.newDocumentBuilder().parse(bookUrl.openStream());
requestedBook = new Book(document);
bookMap.put(id, new SoftReference<>(requestedBook));
} catch (@Nonnull ParserConfigurationException e) {
log.error("Setting up XML parser failed!", e);
} catch (@Nonnull SAXException e) {
log.error("Parsing Book XML file failed!", e);
} catch (@Nonnull IOException e) {
log.error("Reading Book XML file failed!", e);
}
}
return requestedBook;
}
}