/*******************************************************************************
* Copyright (c) 2016 Weasis Team and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nicolas Roduit - initial API and implementation
*******************************************************************************/
package org.weasis.core.api.media.data;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.image.RenderedImage;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import javax.swing.SwingUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.weasis.core.api.Messages;
import org.weasis.core.api.explorer.ObservableEvent;
import org.weasis.core.api.gui.util.Filter;
import org.weasis.core.api.gui.util.JMVUtils;
import org.weasis.core.api.util.StringUtil;
public abstract class Series<E extends MediaElement> extends MediaSeriesGroupNode implements MediaSeries<E> {
private static final Logger LOGGER = LoggerFactory.getLogger(Series.class);
public static final DataFlavor sequenceDataFlavor =
createConstant(DataFlavor.javaJVMLocalObjectMimeType + ";class=" + Series.class.getName(), null); //$NON-NLS-1$
private static final Random RANDOM = new Random();
private static final DataFlavor[] flavors = { sequenceDataFlavor };
private PropertyChangeSupport propertyChange = null;
protected final List<E> medias;
protected final Map<Comparator<E>, List<E>> sortedMedias = new HashMap<>(6);
protected final Comparator<E> mediaOrder;
protected SeriesImporter seriesLoader;
private double fileSize;
public Series(TagW tagID, Object identifier, TagView displayTag) {
this(tagID, identifier, displayTag, null);
}
public Series(TagW tagID, Object identifier, TagView displayTag, int initialCapacity) {
this(tagID, identifier, displayTag, new ArrayList<E>(initialCapacity));
}
public Series(TagW tagID, Object identifier, TagView displayTag, List<E> list) {
this(tagID, identifier, displayTag, list, null);
}
public Series(TagW tagID, Object identifier, TagView displayTag, List<E> list, Comparator<E> mediaOrder) {
super(tagID, identifier, displayTag);
this.mediaOrder = mediaOrder;
List<E> ls = list;
if (ls == null) {
ls = new ArrayList<>();
fileSize = 0.0;
} else if (mediaOrder != null) {
Collections.sort(ls, mediaOrder);
}
medias = Collections.synchronizedList(ls);
}
private static DataFlavor createConstant(String mt, String prn) {
try {
return new DataFlavor(mt, prn, Series.class.getClassLoader()); // $NON-NLS-1$
} catch (Exception e) {
LOGGER.error("Build series flavor", e); //$NON-NLS-1$
return null;
}
}
protected void resetSortedMediasMap() {
if (!sortedMedias.isEmpty()) {
sortedMedias.clear();
}
}
@Override
public List<E> getSortedMedias(Comparator<E> comparator) {
// Do not sort when it is the default order.
if (comparator != null && !comparator.equals(mediaOrder)) {
List<E> sorted = sortedMedias.get(comparator);
if (sorted == null) {
sorted = new ArrayList<>(medias);
Collections.sort(sorted, comparator);
sortedMedias.put(comparator, sorted);
}
return sorted;
}
return medias;
}
@Override
public void add(E media) {
medias.add(media);
resetSortedMediasMap();
}
@Override
public void add(int index, E media) {
medias.add(index, media);
resetSortedMediasMap();
}
@Override
public void addAll(Collection<? extends E> c) {
medias.addAll(c);
resetSortedMediasMap();
}
@Override
public void addAll(int index, Collection<? extends E> c) {
medias.addAll(index, c);
resetSortedMediasMap();
}
@Override
public final E getMedia(MEDIA_POSITION position, Filter<E> filter, Comparator<E> sort) {
List<E> sortedList = getSortedMedias(sort);
synchronized (this) {
if (filter == null) {
int size = sortedList.size();
if (size == 0) {
return null;
}
int pos = 0;
if (MEDIA_POSITION.FIRST.equals(position)) {
pos = 0;
} else if (MEDIA_POSITION.MIDDLE.equals(position)) {
pos = size / 2;
} else if (MEDIA_POSITION.LAST.equals(position)) {
pos = size - 1;
} else if (MEDIA_POSITION.RANDOM.equals(position)) {
pos = RANDOM.nextInt(size);
}
return sortedList.get(pos);
} else {
Iterable<E> iter = filter.filter(sortedList);
Iterator<E> list = iter.iterator();
if (list.hasNext()) {
E val = list.next();
if (MEDIA_POSITION.FIRST.equals(position)) {
return val;
}
int pos = 0;
int size = Filter.size(iter);
if (MEDIA_POSITION.MIDDLE.equals(position)) {
pos = size / 2;
} else if (MEDIA_POSITION.LAST.equals(position)) {
pos = size - 1;
} else if (MEDIA_POSITION.RANDOM.equals(position)) {
pos = RANDOM.nextInt(size);
}
int k = 0;
for (E elem : iter) {
if (k == pos) {
return elem;
}
}
return val;
} else {
return null;
}
}
}
}
public final int getImageIndex(E source, Filter<E> filter, Comparator<E> sort) {
if (source == null) {
return -1;
}
Iterable<E> list = getMedias(filter, sort);
synchronized (this) {
int index = 0;
for (E e : list) {
if (e == source) {
return index;
}
index++;
}
}
return -1;
}
@Override
public final Iterable<E> getMedias(Filter<E> filter, Comparator<E> sort) {
List<E> sortedList = getSortedMedias(sort);
return filter == null ? sortedList : filter.filter(sortedList);
}
@Override
public final List<E> copyOfMedias(Filter<E> filter, Comparator<E> sort) {
List<E> sortedList = getSortedMedias(sort);
return filter == null ? new ArrayList<>(sortedList) : Filter.makeList(filter.filter(sortedList));
}
@Override
public final E getMedia(int index, Filter<E> filter, Comparator<E> sort) {
List<E> sortedList = getSortedMedias(sort);
synchronized (this) {
if (filter == null) {
if (index >= 0 && index < sortedList.size()) {
return sortedList.get(index);
}
} else {
if (index >= 0) {
Iterable<E> iter = filter.filter(sortedList);
int k = 0;
for (E elem : iter) {
if (k == index) {
return elem;
}
k++;
}
}
}
}
return null;
}
@Override
public void dispose() {
// forEach implement synchronized
medias.forEach(m -> {
if (m instanceof ImageElement) {
// Removing from cache will close the image stream
((ImageElement) m).removeImageFromCache();
}
m.dispose();
});
medias.clear();
resetSortedMediasMap();
Optional.ofNullable((Thumbnail) getTagValue(TagW.Thumbnail)).ifPresent(t -> t.dispose());
if (propertyChange != null) {
Arrays.asList(propertyChange.getPropertyChangeListeners())
.forEach(propertyChange::removePropertyChangeListener);
}
seriesLoader = null;
}
@Override
public SeriesImporter getSeriesLoader() {
return seriesLoader;
}
@Override
public void setSeriesLoader(SeriesImporter seriesLoader) {
this.seriesLoader = seriesLoader;
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return flavors;
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return sequenceDataFlavor.equals(flavor);
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if (sequenceDataFlavor.equals(flavor)) {
return this;
}
throw new UnsupportedFlavorException(flavor);
}
public void addPropertyChangeListener(PropertyChangeListener propertychangelistener) {
if (propertyChange == null) {
propertyChange = new PropertyChangeSupport(this);
}
propertyChange.addPropertyChangeListener(propertychangelistener);
}
public void removePropertyChangeListener(PropertyChangeListener propertychangelistener) {
if (propertyChange != null) {
propertyChange.removePropertyChangeListener(propertychangelistener);
}
}
public void firePropertyChange(final ObservableEvent event) {
if (propertyChange != null) {
if (event == null) {
throw new NullPointerException();
}
if (SwingUtilities.isEventDispatchThread()) {
propertyChange.firePropertyChange(event);
} else {
SwingUtilities.invokeLater(() -> propertyChange.firePropertyChange(event));
}
}
}
@Override
public int size(Filter<E> filter) {
synchronized (this) {
return filter == null ? medias.size() : Filter.size(filter.filter(medias));
}
}
@Override
public boolean isOpen() {
Boolean open = (Boolean) getTagValue(TagW.SeriesOpen);
return open == null ? false : open;
}
@Override
public String getToolTips() {
StringBuilder toolTips = new StringBuilder();
toolTips.append("<html>"); //$NON-NLS-1$
E media = this.getMedia(MEDIA_POSITION.MIDDLE, null, null);
if (media instanceof ImageElement) {
ImageElement image = (ImageElement) media;
RenderedImage img = image.getImage();
if (img != null) {
toolTips.append(Messages.getString("Series.img_size")); //$NON-NLS-1$
toolTips.append(StringUtil.COLON_AND_SPACE);
toolTips.append(img.getWidth());
toolTips.append('x');
toolTips.append(img.getHeight());
}
}
toolTips.append("</html>"); //$NON-NLS-1$
return toolTips.toString();
}
protected void addToolTipsElement(StringBuilder toolTips, String title, TagW tag) {
toolTips.append(title);
toolTips.append(StringUtil.COLON_AND_SPACE);
if (tag != null) {
toolTips.append(tag.getFormattedTagValue(getTagValue(tag), null));
}
toolTips.append("<br>"); //$NON-NLS-1$
}
@Override
public void setOpen(boolean open) {
if (this.isOpen() != open) {
setTag(TagW.SeriesOpen, open);
Thumbnail thumb = (Thumbnail) getTagValue(TagW.Thumbnail);
if (thumb != null) {
thumb.repaint();
}
if (!open) {
resetSortedMediasMap();
}
}
}
@Override
public boolean isSelected() {
return JMVUtils.getNULLtoFalse(getTagValue(TagW.SeriesSelected));
}
@Override
public void setSelected(boolean selected, E selectedImage) {
if (this.isSelected() != selected) {
setTag(TagW.SeriesSelected, selected);
Thumbnail thumb = (Thumbnail) getTagValue(TagW.Thumbnail);
if (thumb != null) {
thumb.repaint();
}
}
}
@Override
public boolean isFocused() {
return JMVUtils.getNULLtoFalse(getTagValue(TagW.SeriesFocused));
}
@Override
public void setFocused(boolean focused) {
if (this.isFocused() != focused) {
setTag(TagW.SeriesFocused, focused);
Thumbnail thumb = (Thumbnail) getTagValue(TagW.Thumbnail);
if (thumb != null) {
thumb.repaint();
}
}
}
public boolean hasMediaContains(TagW tag, Object val) {
if (val != null) {
synchronized (this) {
for (int i = 0; i < medias.size(); i++) {
Object val2 = medias.get(i).getTagValue(tag);
if (val.equals(val2)) {
return true;
}
}
}
}
return false;
}
@Override
public E getNearestImage(double location, int offset, Filter<E> filter, Comparator<E> sort) {
return null;
}
@Override
public int getNearestImageIndex(double location, int offset, Filter<E> filter, Comparator<E> sort) {
return -1;
}
public synchronized void setFileSize(double size) {
fileSize = size;
}
@Override
public synchronized double getFileSize() {
return fileSize;
}
@Override
public String getSeriesNumber() {
Integer val = (Integer) getTagValue(TagW.get("SeriesNumber")); //$NON-NLS-1$
return Optional.ofNullable(val).map(String::valueOf).orElseGet(() -> ""); //$NON-NLS-1$
}
}