package org.rr.jeborker.gui.model;
import static org.apache.commons.lang.ObjectUtils.notEqual;
import static org.rr.commons.utils.StringUtil.EMPTY;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.rr.commons.mufs.IResourceHandler;
import org.rr.commons.mufs.ResourceHandlerFactory;
import org.rr.commons.utils.StringUtil;
import org.rr.jeborker.app.FileRefreshBackground;
import org.rr.jeborker.db.item.EbookPropertyItem;
import org.rr.jeborker.gui.MainController;
import org.rr.jeborker.metadata.IMetadataReader;
import org.rr.jeborker.metadata.MetadataHandlerFactory;
import org.rr.jeborker.metadata.MetadataProperty;
import org.rr.jeborker.metadata.MetadataUtils;
import com.l2fprod.common.propertysheet.DefaultProperty;
import com.l2fprod.common.propertysheet.Property;
import com.l2fprod.common.propertysheet.PropertySheetTableModel;
public class EbookSheetPropertyModel extends PropertySheetTableModel {
private static final long serialVersionUID = -4633492433120559387L;
protected static final Comparator<Property> PROPERTY_COMPARATOR = new MetadataPropertyComparator();
private boolean changed = false;
protected IMetadataReader reader;
private IResourceHandler resourceHandler;
protected List<MetadataProperty> allMetadata;
public boolean isChanged() {
List<Property> properties = this.getProperties();
for (Property property : properties) {
if(property instanceof EbookSheetProperty && ((EbookSheetProperty)property).isChanged()) {
return true;
}
}
return changed;
}
public void setChanged(boolean changed) {
this.changed = changed;
}
@Override
public void addProperty(int index, Property property) {
if(!isCoverProperty(property)) {
super.addProperty(index, property);
}
if(property instanceof EbookSheetProperty) {
MetadataProperty metadataProperty = ((EbookSheetProperty)property).metadataProperty;
allMetadata.add(metadataProperty);
}
changed = true;
}
@Override
public void addProperty(Property property) {
if(!isCoverProperty(property)) {
super.addProperty(property);
}
if(property instanceof EbookSheetProperty) {
MetadataProperty metadataProperty = ((EbookSheetProperty)property).metadataProperty;
allMetadata.add(metadataProperty);
}
changed = true;
}
@Override
public void removeProperty(Property property) {
property.setValue(EMPTY);
super.removeProperty(property);
if (property instanceof EbookSheetProperty) {
List<MetadataProperty> subProperties = ((EbookSheetProperty) property).getMetadataProperties();
for (MetadataProperty metadataProperty : subProperties) {
allMetadata.remove(metadataProperty);
}
allMetadata.remove(((EbookSheetProperty) property).metadataProperty);
}
changed = true;
}
@Override
public void setProperties(Property[] newProperties) {
super.setProperties(newProperties);
changed = false;
}
/**
* The rating property.
* @return The desired rating property or <code>null</code> if no rating property is exists.
*/
public Property getRatingProperty() {
List<Property> properties = getProperties();
for (Property property : properties) {
if(property.getName().toLowerCase().contains("rating")) {
return property;
}
}
return null;
}
/**
* Get a fake cover property if there is a cover {@link MetadataProperty} present.
* The fake property did not contains any cover data.
*
* @return The desired property or <code>null</code> if no cover property exists.
*/
public Property getCoverProperty() {
List<MetadataProperty> allMetadata = getAllMetadata();
for(MetadataProperty metadata : allMetadata) {
if(IMetadataReader.COMMON_METADATA_TYPES.COVER.getName().equalsIgnoreCase(metadata.getName())) {
final DefaultProperty coverProperty = new DefaultProperty();
coverProperty.setDisplayName("cover");
coverProperty.setName(metadata.getName());
coverProperty.setEditable(true);
coverProperty.setDeletable(false);
coverProperty.setValue(null);
coverProperty.setShortDescription("cover");
return coverProperty;
}
}
return null;
}
/**
* The rating property.
* @return The desired rating property or <code>null</code> if no rating property is exists.
*/
public int getRatingIndex() {
List<Property> properties = getProperties();
for (int i = 0; i < properties.size(); i++) {
if(properties.get(i).getName().toLowerCase().contains("rating")) {
return i;
}
}
return -1;
}
public void loadProperties(EbookPropertyItem item) {
this.resourceHandler = ResourceHandlerFactory.getResourceHandler(item.getFile());
if(resourceHandler.exists()) {
this.reader = MetadataHandlerFactory.getReader(resourceHandler);
Property[] newProperties = createProperties(resourceHandler, item, reader);
setProperties(newProperties);
} else {
setProperties(new Property[0]);
FileRefreshBackground.getInstance().addEbook(item);
}
}
public IMetadataReader getMetadataReader() {
return reader;
}
public IResourceHandler getResourceHandler() {
return resourceHandler;
}
/**
* Get the display name for the given property.
* @param property The property where the display name should be gotten for.
* @return The desired display name. Never returns <code>null</code>.
*/
public String getDisplayName(Property property) {
if(property instanceof DefaultProperty) {
String displayName = ((DefaultProperty)property).getDisplayName();
if(displayName != null) {
return displayName;
}
}
return EMPTY;
}
/**
* Get the {@link EbookPropertyItem} assigned to the given {@link Property} instance.
* @param property The {@link Property} instance where none, one or more {@link EbookPropertyItem} assigned to.
* @return The list with all assigned {@link EbookPropertyItem}s. Never returns <code>null</code>.
*/
public List<EbookPropertyItem> getEbookPropertyItems(Property property) {
if(property instanceof EbookSheetProperty) {
List<EbookPropertyItem> ebookPropertyItems = ((EbookSheetProperty)property).getEbookPropertyItems();
return ebookPropertyItems;
}
return Collections.emptyList();
}
/**
* Get the {@link MetadataProperty} assigned to the given {@link Property} instance.
* @param property The {@link Property} instance where none, one or more {@link MetadataProperty} assigned to.
* @return The list with all assigned {@link MetadataProperty}. Never returns <code>null</code>.
*/
public List<MetadataProperty> getMetadataProperties(Property property) {
if(property instanceof EbookSheetProperty) {
List<MetadataProperty> metadataProperties = ((EbookSheetProperty)property).getMetadataProperties();
return metadataProperties;
}
return Collections.emptyList();
}
public List<MetadataProperty> getAllMetadata() {
return allMetadata;
}
/**
* Get the cover bytes from the metadata.
* @return The desired cover bytes or <code>null</code> if no cover bytes are present.
*/
public byte[] getCover() {
IMetadataReader metadataReader = getMetadataReader();
if(metadataReader != null) {
List<MetadataProperty> metadataByType = metadataReader.getMetadataByType(false, allMetadata, IMetadataReader.COMMON_METADATA_TYPES.COVER);
if(metadataByType != null && !metadataByType.isEmpty()) {
MetadataProperty metadataProperty = metadataByType.get(0);
if(metadataProperty.getValues() != null && !metadataProperty.getValues().isEmpty()) {
Object value = metadataProperty.getValues().get(0);
if(value instanceof byte[]) {
return (byte[]) value;
}
}
}
}
return null;
}
/**
* Creates all Property elements supported by the ebook format given
* with the {@link EbookPropertyItem} parameter.
* @param item The item where the {@link Property} array should be created for.
* @return A couple of properties for the given {@link EbookPropertyItem} never returns <code>null</code>.
*/
private Property[] createProperties(final IResourceHandler resourceHandler, final EbookPropertyItem item, final IMetadataReader reader) {
if(resourceHandler == null) {
return new Property[0];
}
final List<Property> properties = setupMetadata(Collections.singletonList(item), reader);
final DefaultProperty fileNameProperty = new DefaultProperty();
fileNameProperty.setDisplayName(Bundle.getString("EbookPropertySheetProperty.property.name.file"));
fileNameProperty.setName("file");
fileNameProperty.setEditable(false);
fileNameProperty.setDeletable(false);
fileNameProperty.setValue(resourceHandler);
fileNameProperty.setShortDescription(resourceHandler != null ? resourceHandler.toString() : EMPTY);
properties.add(fileNameProperty);
Collections.sort(properties, PROPERTY_COMPARATOR);
return properties.toArray(new Property[properties.size()]);
}
/**
* attaches a property for each metadata item of the resource given with the
* resourceLoader parameter.
* @param result The list where the metadata properties should be attched to.
* @param resourceLoader The {@link IResourceHandler} providing the ebook data.
*/
protected List<Property> setupMetadata(final List<EbookPropertyItem> items, final IMetadataReader reader) {
final ArrayList<Property> result = new ArrayList<>(items.size() + 1);
final List<MetadataProperty> allMetadata = new ArrayList<>(this.allMetadata = reader.readMetadata());
for (int i = 0; i < allMetadata.size(); i++) {
final MetadataProperty metadataProperty = allMetadata.get(i);
final List<Object> values = metadataProperty.getValues();
if(values.size() == 1) {
//find properties with the same name and value to merge them in the sheet view.
List<MetadataProperty> mergedProperties = MetadataUtils.getSameProperties(metadataProperty, allMetadata);
if(mergedProperties.size() > 1) {
EbookSheetProperty property = new MultipleEbookSheetProperty(mergedProperties, items);
result.add(property);
allMetadata.removeAll(mergedProperties);
i--;
} else {
if(metadataProperty.isVisible()) {
Property property = createProperty(metadataProperty, items, 0);
result.add(property);
}
}
} else if(metadataProperty.isVisible()) {
for (int j=0; j < values.size(); j++) {
Property property = createProperty(metadataProperty, items, j);
result.add(property);
}
}
}
return result;
}
/**
* tests if the given {@link Property} instance is a cover property instance.
* @param property The {@link Property} instance to be tested.
* @return <code>true</code> if the given {@link Property} is a cover and <code>false</code> otherwise.
*/
private boolean isCoverProperty(Property property) {
if(IMetadataReader.COMMON_METADATA_TYPES.COVER.getName().equalsIgnoreCase(property.getName())) {
return true;
}
return false;
}
public static Property createProperty(final MetadataProperty metadataProperty, final List<EbookPropertyItem> items, int valueIndex) {
return new EbookSheetProperty(metadataProperty, items, valueIndex);
}
protected static class MetadataPropertyComparator implements Comparator<Property> {
@Override
public int compare(Property p1, Property p2) {
String name1 = getOrderValue(p1);
String name2 = getOrderValue(p2);
return name1.compareTo(name2);
}
private String getOrderValue(Property p1) {
String name1 = StringUtil.toString(p1.getName()).toLowerCase();
if(name1.equals("file")) {
return "01";
} else if(name1.equals("author") || name1.equals("creator / aut")) {
return "02";
} else if(name1.equals("title")) {
return "03";
} else {
return "50" + name1;
}
}
}
/**
* This {@link EbookSheetProperty} derivat can handle multiple {@link MetadataProperty} instances.
* The first instance in the given {@link MetadataProperty} array is this one, where the name
* and the description are taken from. The value will be set to each of the given {@link MetadataProperty}
* instances. This allows to merge different properties to be shown as one.
*/
protected static class MultipleEbookSheetProperty extends EbookSheetProperty {
private static final long serialVersionUID = 3047729348480097722L;
final List<MetadataProperty> metadataProperties;
public MultipleEbookSheetProperty(final List<MetadataProperty> metadataProperties, final List<EbookPropertyItem> items) {
super(metadataProperties.get(0), items, 0);
this.metadataProperties = metadataProperties;
for (MetadataProperty metadataProperty : metadataProperties) {
if(!metadataProperty.isEditable()) {
this.setEditable(false);
}
}
}
@Override
public void setValue(Object value) {
Object oldValue = getValue();
super.setValue(value);
if(notEqual(value, oldValue)) {
for (MetadataProperty metadataProperty : metadataProperties) {
metadataProperty.setValue(value, 0);
}
this.setChanged(true);
}
}
/**
* Gets the encapsulated {@link MetadataProperty} which holds the current value state.
* @return The encapsulated {@link MetadataProperty} or <code>null</code> if no one existing.
*/
public List<MetadataProperty> getMetadataProperties() {
return metadataProperties;
}
}
public void dispose() {
super.dispose();
if(this.reader != null) {
this.reader = null;
}
}
/**
* search for the property which has the ebook file as value
* @param properties The properties to be searched.
* @return The desired {@link IResourceHandler} or <code>null</code> if no {@link IResourceHandler} could be found.
*/
public List<IResourceHandler> getPropertyResourceHandler() {
final List<Property> properties = getProperties();
for (Property property : properties) {
if(property.getValue() instanceof IResourceHandler) {
return Collections.singletonList((IResourceHandler) property.getValue());
}
}
final List<EbookPropertyItem> selectedEbookPropertyItems = MainController.getController().getSelectedEbookPropertyItems();
final List<IResourceHandler> result = new ArrayList<>(selectedEbookPropertyItems.size());
for (EbookPropertyItem ebookPropertyItem : selectedEbookPropertyItems) {
result.add(ebookPropertyItem.getResourceHandler());
}
return result;
}
}