Java tutorial
/* * This file is part of JimCat. * * JimCat 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 version 2. * * JimCat 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 JimCat; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jimcat.gui.histogram.image; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.lang.ObjectUtils; import org.jimcat.gui.ImageControl; import org.jimcat.gui.ViewControl; import org.jimcat.gui.ViewFilterListener; import org.jimcat.gui.histogram.HistogramModel; import org.jimcat.gui.histogram.HistogramModelEvent; import org.jimcat.gui.histogram.HistogramModelPath; import org.jimcat.gui.histogram.HistogramModel.ScaleMark; import org.jimcat.model.Image; import org.jimcat.model.filter.metadata.PictureTakenFilter; import org.jimcat.model.filter.metadata.PictureTakenFilter.Type; import org.jimcat.model.libraries.ImageLibrary; import org.jimcat.model.notification.BeanChangeEvent; import org.jimcat.model.notification.BeanProperty; import org.jimcat.model.notification.CollectionListener; import org.joda.time.DateTime; import org.joda.time.Days; import org.joda.time.Months; import org.joda.time.Weeks; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; /** * This Dimension is used within the ImageHistogram widget to display images * date taken spreading. It also controlles date taken filter functions. * * $Id$ * * @author Herbert */ public class DateTakenDimension extends Dimension { /** * constants for resolution - DAYS */ public static final int DAYS = 0; /** * constants for resolution - WEEKS */ public static final int WEEKS = 1; /** * constants for resolution - MONTHS */ public static final int MONTHS = 2; /** * amoung of resolutions supported */ private static final int RESOLUTION_COUNT = 3; /** * the format used to print day labels */ private static final DateTimeFormatter DAY_LABEL_FORMATTER = DateTimeFormat.forPattern("dd. MMM yy"); /** * the format used to print week labels */ private static final DateTimeFormatter WEEK_LABEL_FORMATTER = DateTimeFormat.forPattern("w / yy"); /** * the format used to print month labels */ private static final DateTimeFormatter MONTH_LABEL_FORMATTER = DateTimeFormat.forPattern("MMM yy"); /** * the view control of this jimcat instance */ private ViewControl control; /** * the value array */ private int values[][]; /** * a vector containing max values */ private int maxValues[]; /** * the minimum date - used for labeling and calculations (resolution * dependend) */ private DateTime minDate[]; /** * the lower limit set - null if not set */ private DateTime lowerLimit; /** * the higher limit set - null if not set */ private DateTime higherLimit; /** * the number of this dimension */ private int dimensionNumber; /** * constructs a new dimension contained in the given ImageHistogram. * * @param histogram * @param viewControl * @param imageControl * @param number */ public DateTakenDimension(ImageHistogram histogram, ViewControl viewControl, ImageControl imageControl, int number) { super(histogram); control = viewControl; control.addViewFilterListener(new FilterListener()); dimensionNumber = number; ImageLibrary library = imageControl.getLibrary(); library.addListener(new ImageLibraryListener()); reloadValues(library); } /** * this methode will reload statistical information from the library * * @param library */ private void reloadValues(ImageLibrary library) { // get images List<Image> images = new ArrayList<Image>(library.getAll()); // 1) find min / max date int i = 0; DateTime min = null; DateTime max = null; while (min == null && i < images.size()) { min = getDateTaken(images.get(i)); max = min; i++; } if (min == null || max == null) { // result is an empty vector values = new int[RESOLUTION_COUNT][0]; maxValues = new int[RESOLUTION_COUNT]; minDate = null; fireStructureChangedEvent(); return; } // iterate through images for (; i < images.size(); i++) { Image img = images.get(i); DateTime date = getDateTaken(img); if (date != null && min.isAfter(date)) { min = date; } if (date != null && max.isBefore(date)) { max = date; } } // save min dates // get references // must be a pure day DateTime dayRef = new DateTime(2000, 1, 1, 0, 0, 0, 0); // must be a monday DateTime weekRef = new DateTime(2000, 1, 3, 0, 0, 0, 0); // must be the first of any month DateTime monthRef = dayRef; minDate = new DateTime[RESOLUTION_COUNT]; minDate[DAYS] = dayRef.plusDays(Days.daysBetween(dayRef, min).getDays()); minDate[WEEKS] = weekRef.plusWeeks(Weeks.weeksBetween(weekRef, min).getWeeks()); minDate[MONTHS] = monthRef.plusMonths(Months.monthsBetween(monthRef, min).getMonths()); // 2) build arrays values = new int[RESOLUTION_COUNT][]; values[DAYS] = new int[Days.daysBetween(minDate[DAYS], max).getDays() + 1]; values[WEEKS] = new int[Weeks.weeksBetween(minDate[WEEKS], max).getWeeks() + 1]; values[MONTHS] = new int[Months.monthsBetween(minDate[MONTHS], max).getMonths() + 1]; // 3) fill in values for (Image img : images) { // extract date DateTime date = getDateTaken(img); if (date != null) { values[DAYS][Days.daysBetween(minDate[DAYS], date).getDays()]++; values[WEEKS][Weeks.weeksBetween(minDate[WEEKS], date).getWeeks()]++; values[MONTHS][Months.monthsBetween(minDate[MONTHS], date).getMonths()]++; } } // 4) get max values maxValues = new int[RESOLUTION_COUNT]; for (int j = 0; j < RESOLUTION_COUNT; j++) { maxValues[j] = values[j][0]; for (int k = 1; k < values[j].length; k++) { if (maxValues[j] < values[j][k]) { maxValues[j] = values[j][k]; } } } // 6) notify listners fireStructureChangedEvent(); } /** * exctract date taken out of given image * * @param img * @return the date taken */ private DateTime getDateTaken(Image img) { if (img.getExifMetadata() != null) { return img.getExifMetadata().getDateTaken(); } return null; } /** * converte indizes from one resolution to another * * @see org.jimcat.gui.histogram.image.Dimension#convertIndex(int, int, int) */ @Override public int convertIndex(int fromResolution, int toResolution, int index) { // shortcut if (fromResolution == toResolution) { return index; } // index => date DateTime date = indexToDate(fromResolution, index); // date => index int result = dateToIndex(toResolution, date); return Math.max(Math.min(result, getBucketCount(toResolution)), 0); } /** * count buckets of resolution * * @see org.jimcat.gui.histogram.image.Dimension#getBucketCount(int) */ @Override public int getBucketCount(int resolution) { return values[resolution].length; } /** * get resolution count * * @see org.jimcat.gui.histogram.image.Dimension#getCountResolutions() */ @Override public int getCountResolutions() { return RESOLUTION_COUNT; } /** * get index of higher limit for given resolution * * @see org.jimcat.gui.histogram.image.Dimension#getHigherLimiter(int) */ @Override public int getHigherLimiter(int resolution) { if (higherLimit == null) { return HistogramModel.NO_LIMIT; } return dateToIndex(resolution, higherLimit); } /** * get initial index for resolution 0 * * @see org.jimcat.gui.histogram.image.Dimension#getInitialIndex() */ @Override public int getInitialIndex() { return getBucketCount(0); } /** * get label for mark ... * * @see org.jimcat.gui.histogram.image.Dimension#getLabelForMark(int, int) */ @Override public String getLabelForMark(int resolution, int index) { switch (resolution) { case DAYS: return getDayLabel(index); case WEEKS: return getWeekLabel(index); case MONTHS: return getMonthLabel(index); } return null; } /** * get a label text for given index * * @param index * @return label text for given index */ private String getDayLabel(int index) { DateTime date = indexToDate(DAYS, index); int day = date.getDayOfMonth(); if (day == 1 || day == 15) { return DAY_LABEL_FORMATTER.print(date); } return null; } /** * get label text for given index * * @param index * @return the label text for the given index */ private String getWeekLabel(int index) { DateTime date = indexToDate(WEEKS, index); int week = date.getWeekOfWeekyear(); if (week == 1 || week % 10 == 0) { return WEEK_LABEL_FORMATTER.print(date); } return null; } /** * get label text for given index * * @param index * @return the label text for given index */ private String getMonthLabel(int index) { DateTime date = indexToDate(MONTHS, index); int month = date.getMonthOfYear(); if (month % 3 == 0) { return MONTH_LABEL_FORMATTER.print(date); } return null; } /** * get lower limit using given resolution * * @see org.jimcat.gui.histogram.image.Dimension#getLowerLimiter(int) */ @Override public int getLowerLimiter(int resolution) { if (lowerLimit == null) { return HistogramModel.NO_LIMIT; } return dateToIndex(resolution, lowerLimit); } /** * get mark for index * * @see org.jimcat.gui.histogram.image.Dimension#getMarkFor(int, int) */ @Override public ScaleMark getMarkFor(int resolution, int index) { ScaleMark result = ScaleMark.SMALL; // get corresponding date DateTime date = indexToDate(resolution, index); if (date == null) { // no data available return ScaleMark.NONE; } if (resolution == DAYS) { int day = date.getDayOfMonth(); if (day == 1 || day == 15) { result = ScaleMark.LABEL; } } else if (resolution == WEEKS) { int week = date.getWeekOfWeekyear(); if (week == 1 || week % 10 == 0) { result = ScaleMark.LABEL; } } else if (resolution == MONTHS) { int month = date.getMonthOfYear(); if (month % 3 == 0) { result = ScaleMark.LABEL; } } return result; } /** * get static name * * @see org.jimcat.gui.histogram.image.Dimension#getName() */ @Override public String getName() { return "Date Taken"; } /** * get static names for resolutions * * @see org.jimcat.gui.histogram.image.Dimension#getNameFor(int) */ @Override public String getNameFor(int resolution) { switch (resolution) { case DAYS: return "Days"; case WEEKS: return "Weeks"; default: return "Months"; } } /** * get value for given index * * @see org.jimcat.gui.histogram.image.Dimension#getValueAt(int, int) */ @Override public float getValueAt(int resolution, int index) throws IllegalArgumentException { return getAbsoluteValueAt(resolution, index) / (float) maxValues[resolution]; } /** * get absolute value at given index * * @see org.jimcat.gui.histogram.image.Dimension#getAbsoluteValueAt(int, * int) */ @Override public int getAbsoluteValueAt(int resolution, int index) throws IllegalArgumentException { try { return values[resolution][index]; } catch (ArrayIndexOutOfBoundsException e) { throw new IllegalArgumentException("unsupported resolution: " + resolution + " / index: " + index); } } /** * set higher limit * * @see org.jimcat.gui.histogram.image.Dimension#setHigherLimiter(int, int) */ @Override public void setHigherLimiter(int resolution, int index) throws IllegalArgumentException { DateTime newLimit = null; if (index != HistogramModel.NO_LIMIT) { newLimit = indexToDate(resolution, index); } if (!ObjectUtils.equals(higherLimit, newLimit)) { higherLimit = newLimit; PictureTakenFilter filter = null; if (newLimit != null) { filter = new PictureTakenFilter(Type.BEFORE, newLimit); } control.setPictureTakenBefore(filter); } } /** * set lower limit * * @see org.jimcat.gui.histogram.image.Dimension#setLowerLimiter(int, int) */ @Override public void setLowerLimiter(int resolution, int index) throws IllegalArgumentException { DateTime newLimit = null; if (index != HistogramModel.NO_LIMIT) { newLimit = indexToDate(resolution, index); } if (!ObjectUtils.equals(lowerLimit, newLimit)) { lowerLimit = newLimit; PictureTakenFilter filter = null; if (newLimit != null) { filter = new PictureTakenFilter(Type.AFTER, newLimit); } control.setPictureTakenAfter(filter); } } /** * converte a given resolution / index pair to a date time * * @param resolution * @param index * @return the converted date */ private DateTime indexToDate(int resolution, int index) { if (minDate == null) { return null; } // bring index to date DateTime date = null; switch (resolution) { case DAYS: date = minDate[DAYS].plusDays(index); break; case WEEKS: date = minDate[WEEKS].plusWeeks(index); break; default: date = minDate[MONTHS].plusMonths(index); break; } return date; } /** * converte a given resolution / date pair to a index * * @param resolution * @param date * @return the converted index */ private int dateToIndex(int resolution, DateTime date) { if (date == null || minDate == null) { return 0; } // convert to new resolution switch (resolution) { case DAYS: return Days.daysBetween(minDate[DAYS], date).getDays(); case WEEKS: return Weeks.weeksBetween(minDate[WEEKS], date).getWeeks(); default: return Months.monthsBetween(minDate[MONTHS], date).getMonths(); } } /** * small class listening to filter changes */ private class FilterListener implements ViewFilterListener { /** * react on filter changes * * @see org.jimcat.gui.ViewFilterListener#filterChanges(org.jimcat.gui.ViewControl) */ public void filterChanges(ViewControl viewControl) { PictureTakenFilter after = viewControl.getPictureTakenAfter(); PictureTakenFilter before = viewControl.getPictureTakenBefore(); DateTime lower = null; if (after != null) { lower = after.getLimitDate(); } DateTime higher = null; if (before != null) { higher = before.getLimitDate(); } // update limits if (!ObjectUtils.equals(lower, lowerLimit)) { lowerLimit = lower; // inform listener HistogramModelPath path = new HistogramModelPath(dimensionNumber, 0, 0); HistogramModelEvent event = new HistogramModelEvent(null, path, false); fireLimitChangedEvent(event); } if (!ObjectUtils.equals(higher, higherLimit)) { higherLimit = higher; // inform listener HistogramModelPath path = new HistogramModelPath(dimensionNumber, 0, 0); HistogramModelEvent event = new HistogramModelEvent(null, path, true); fireLimitChangedEvent(event); } } } /** * the listener observing images states */ private class ImageLibraryListener implements CollectionListener<Image, ImageLibrary> { /** * @param collection * @see org.jimcat.model.notification.CollectionListener#basementChanged(org.jimcat.model.notification.ObservableCollection) */ public void basementChanged(ImageLibrary collection) { // no way => reload values reloadValues(collection); } /** * react on added elements * * @param collection * @param elements * * @see org.jimcat.model.notification.CollectionListener#elementsAdded(org.jimcat.model.notification.ObservableCollection, * java.util.Set) */ public void elementsAdded(ImageLibrary collection, Set<Image> elements) { // list of changed paths Set<HistogramModelPath> paths = new HashSet<HistogramModelPath>(); // process elements for (Image element : elements) { // check if element fit in bounds DateTime date = getDateTaken(element); // avoid null values if (date == null) { continue; } for (int res = 0; res < values.length; res++) { // check bounds int index = dateToIndex(res, date); if (index < 0 || index >= values[res].length) { // there is a big change => reload all values reloadValues(collection); return; } // increment value values[res][index]++; int newValue = values[res][index]; if (newValue > maxValues[res]) { maxValues[res] = newValue; } paths.add(new HistogramModelPath(dimensionNumber, res, index)); } } // send event HistogramModelEvent event = new HistogramModelEvent(getHistogram(), paths); fireValueChangedEvent(event); } /** * react on removed elements * * @param collection * @param elements * * @see org.jimcat.model.notification.CollectionListener#elementsRemoved(org.jimcat.model.notification.ObservableCollection, * java.util.Set) */ public void elementsRemoved(ImageLibrary collection, Set<Image> elements) { // list of changed paths Set<HistogramModelPath> paths = new HashSet<HistogramModelPath>(); // process images for (Image element : elements) { // extract element date taken DateTime date = getDateTaken(element); // avoid null values if (date == null) { continue; } for (int res = 0; res < values.length; res++) { // check bounds int index = dateToIndex(res, date); // increment value int oldValue = values[res][index]; values[res][index]--; if (oldValue == maxValues[res]) { // finde new Maximum int max = values[res][0]; for (int i = 1; i < values[res].length; i++) { int cur = values[res][i]; if (cur > max) { max = cur; } } maxValues[res] = max; } // check borders if (values[res][0] == 0 || values[res][getBucketCount(res) - 1] == 0) { // a border bucket is out of values => massive change reloadValues(collection); return; } paths.add(new HistogramModelPath(dimensionNumber, res, index)); } } // fire event HistogramModelEvent event = new HistogramModelEvent(getHistogram(), paths); fireValueChangedEvent(event); } /** * react on updates * * @param collection * @param events * * @see org.jimcat.model.notification.CollectionListener#elementsUpdated(org.jimcat.model.notification.ObservableCollection, * java.util.List) */ public void elementsUpdated(ImageLibrary collection, List<BeanChangeEvent<Image>> events) { for (BeanChangeEvent<Image> event : events) { if (event.getProperty() == BeanProperty.IMAGE_EXIF_META) { // it works but may be slow ... reloadValues(collection); return; } } } } }