All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.calendarfx.util.Util Maven / Gradle / Ivy

There is a newer version: 11.12.7
Show newest version
/*
 *  Copyright (C) 2017 Dirk Lemmermann Software & Consulting (dlsc.com)
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *          http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.calendarfx.util;

import com.calendarfx.view.DateControl;
import com.calendarfx.view.Messages;
import com.google.ical.values.DateValue;
import com.google.ical.values.Frequency;
import com.google.ical.values.RRule;
import com.google.ical.values.Weekday;
import com.google.ical.values.WeekdayNum;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakListener;
import javafx.beans.property.Property;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.control.ScrollBar;

import java.lang.ref.WeakReference;
import java.text.MessageFormat;
import java.text.ParseException;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.List;
import java.util.Objects;

import static java.time.temporal.ChronoField.DAY_OF_WEEK;

/**
 * Utility methods.
 */
public class Util {

    /**
     * An interface used for converting an object of one type to an object
     * of another type.
     *
     * @param  the first (left) type
     * @param  the second (right) type
     */
    public interface Converter {

        L toLeft(R right);

        R toRight(L left);
    }

    /**
     * Converts the given recurrence rule (according to RFC 2445) into a human
     * readable text, e.g. "RRULE:FREQ=DAILY;" becomes "Every day".
     *
     * @param rrule     the rule
     * @param startDate the start date for the rule
     * @return a nice text describing the rule
     */
    public static String convertRFC2445ToText(String rrule,
                                              LocalDate startDate) {

        try {
            RRule rule = new RRule(rrule);
            StringBuilder sb = new StringBuilder();

            String granularity = ""; //$NON-NLS-1$
            String granularities = ""; //$NON-NLS-1$

            switch (rule.getFreq()) {
                case DAILY:
                    granularity = Messages.getString("Util.DAY"); //$NON-NLS-1$
                    granularities = Messages.getString("Util.DAYS"); //$NON-NLS-1$
                    break;
                case MONTHLY:
                    granularity = Messages.getString("Util.MONTH"); //$NON-NLS-1$
                    granularities = Messages.getString("Util.MONTHS"); //$NON-NLS-1$
                    break;
                case WEEKLY:
                    granularity = Messages.getString("Util.WEEK"); //$NON-NLS-1$
                    granularities = Messages.getString("Util.WEEKS"); //$NON-NLS-1$
                    break;
                case YEARLY:
                    granularity = Messages.getString("Util.YEAR"); //$NON-NLS-1$
                    granularities = Messages.getString("Util.YEARS"); //$NON-NLS-1$
                    break;
                case HOURLY:
                    granularity = Messages.getString("Util.HOUR"); //$NON-NLS-1$
                    granularities = Messages.getString("Util.HOURS"); //$NON-NLS-1$
                    break;
                case MINUTELY:
                    granularity = Messages.getString("Util.MINUTE"); //$NON-NLS-1$
                    granularities = Messages.getString("Util.MINUTES"); //$NON-NLS-1$
                    break;
                case SECONDLY:
                    granularity = Messages.getString("Util.SECOND"); //$NON-NLS-1$
                    granularities = Messages.getString("Util.SECONDS"); //$NON-NLS-1$
                    break;
            }

            int interval = rule.getInterval();
            if (interval > 1) {
                sb.append(MessageFormat.format(Messages.getString("Util.EVERY_PLURAL"), //$NON-NLS-1$
                        rule.getInterval(), granularities));
            } else {
                sb.append(MessageFormat.format(Messages.getString("Util.EVERY_SINGULAR"), granularity)); //$NON-NLS-1$
            }

            /*
             * Weekdays
             */

            if (rule.getFreq().equals(Frequency.WEEKLY)) {
                List byDay = rule.getByDay();
                if (!byDay.isEmpty()) {
                    sb.append(Messages.getString("Util.ON_WEEKDAY")); //$NON-NLS-1$
                    for (int i = 0; i < byDay.size(); i++) {
                        WeekdayNum num = byDay.get(i);
                        sb.append(makeHuman(num.wday));
                        if (i < byDay.size() - 1) {
                            sb.append(", "); //$NON-NLS-1$
                        }
                    }
                }
            }

            if (rule.getFreq().equals(Frequency.MONTHLY)) {

                if (rule.getByMonthDay().length > 0) {

                    int day = rule.getByMonthDay()[0];
                    sb.append(Messages.getString("Util.ON_MONTH_DAY")); //$NON-NLS-1$
                    sb.append(day);

                } else if (!rule.getByDay().isEmpty()) {

                    /*
                     * We only support one day.
                     */
                    WeekdayNum num = rule.getByDay().get(0);

                    sb.append(MessageFormat.format(Messages.getString("Util.ON_MONTH_WEEKDAY"), //$NON-NLS-1$
                            makeHuman(num.num), makeHuman(num.wday)));

                }
            }

            if (rule.getFreq().equals(Frequency.YEARLY)) {
                sb.append(MessageFormat.format(Messages.getString("Util.ON_DATE"), DateTimeFormatter //$NON-NLS-1$
                        .ofPattern(Messages.getString("Util.MONTH_AND_DAY_FORMAT")).format(startDate))); //$NON-NLS-1$
            }

            int count = rule.getCount();
            if (count > 0) {
                if (count == 1) {
                    return Messages.getString("Util.ONCE"); //$NON-NLS-1$
                } else {
                    sb.append(MessageFormat.format(Messages.getString("Util.TIMES"), count)); //$NON-NLS-1$
                }
            } else {
                DateValue until = rule.getUntil();
                if (until != null) {
                    LocalDate localDate = LocalDate.of(until.year(),
                            until.month(), until.day());
                    sb.append(
                            MessageFormat.format(Messages.getString("Util.UNTIL_DATE"), //$NON-NLS-1$
                                    DateTimeFormatter
                                            .ofLocalizedDate(FormatStyle.LONG)
                                            .format(localDate)));
                }
            }

            return sb.toString();
        } catch (ParseException e) {
            e.printStackTrace();
            return Messages.getString("Util.INVALID_RULE"); //$NON-NLS-1$
        }
    }

    private static String makeHuman(Weekday wday) {
        switch (wday) {
            case FR:
                return Messages.getString("Util.FRIDAY"); //$NON-NLS-1$
            case MO:
                return Messages.getString("Util.MONDAY"); //$NON-NLS-1$
            case SA:
                return Messages.getString("Util.SATURDAY"); //$NON-NLS-1$
            case SU:
                return Messages.getString("Util.SUNDAY"); //$NON-NLS-1$
            case TH:
                return Messages.getString("Util.THURSDAY"); //$NON-NLS-1$
            case TU:
                return Messages.getString("Util.TUESDAY"); //$NON-NLS-1$
            case WE:
                return Messages.getString("Util.WEDNESDAY"); //$NON-NLS-1$
            default:
                throw new IllegalArgumentException("unknown weekday: " + wday); //$NON-NLS-1$
        }
    }

    private static String makeHuman(int num) {
        switch (num) {
            case 1:
                return Messages.getString("Util.FIRST"); //$NON-NLS-1$
            case 2:
                return Messages.getString("Util.SECOND"); //$NON-NLS-1$
            case 3:
                return Messages.getString("Util.THIRD"); //$NON-NLS-1$
            case 4:
                return Messages.getString("Util.FOURTH"); //$NON-NLS-1$
            case 5:
                return Messages.getString("Util.FIFTH"); //$NON-NLS-1$
            default:
                return Integer.toString(num);
        }
    }

    /**
     * Searches for a {@link ScrollBar} of the given orientation (vertical, horizontal)
     * somewhere in the containment hierarchy of the given parent node.
     *
     * @param parent      the parent node
     * @param orientation the orientation (horizontal, vertical)
     * @return a scrollbar or null if none can be found
     */
    public static ScrollBar findScrollBar(Parent parent, Orientation orientation) {
        for (Node node : parent.getChildrenUnmodifiable()) {
            if (node instanceof ScrollBar) {
                ScrollBar b = (ScrollBar) node;
                if (b.getOrientation().equals(orientation)) {
                    return b;
                }
            }

            if (node instanceof Parent) {
                ScrollBar b = findScrollBar((Parent) node, orientation);
                if (b != null) {
                    return b;
                }
            }
        }

        return null;
    }

    /**
     * Adjusts the given date to a new date that marks the beginning of the week where the
     * given date is located. If "Monday" is the first day of the week and the given date
     * is a "Wednesday" then this method will return a date that is two days earlier than the
     * given date.
     *
     * @param date           the date to adjust
     * @param firstDayOfWeek the day of week that is considered the start of the week ("Monday" in Germany, "Sunday" in the US)
     * @return the date of the first day of the week
     * @see #adjustToLastDayOfWeek(LocalDate, DayOfWeek)
     * @see DateControl#getFirstDayOfWeek()
     */
    public static LocalDate adjustToFirstDayOfWeek(LocalDate date, DayOfWeek firstDayOfWeek) {
        LocalDate newDate = date.with(DAY_OF_WEEK, firstDayOfWeek.getValue());
        if (newDate.isAfter(date)) {
            newDate = newDate.minusWeeks(1);
        }

        return newDate;
    }

    /**
     * Adjusts the given date to a new date that marks the end of the week where the
     * given date is located. If "Monday" is the first day of the week and the given date
     * is a "Wednesday" then this method will return a date that is four days later than the
     * given date. This method calculates the first day of the week and then adds six days
     * to it.
     *
     * @param date           the date to adjust
     * @param firstDayOfWeek the day of week that is considered the start of the week ("Monday" in Germany, "Sunday" in the US)
     * @return the date of the first day of the week
     * @see #adjustToFirstDayOfWeek(LocalDate, DayOfWeek)
     * @see DateControl#getFirstDayOfWeek()
     */
    public static LocalDate adjustToLastDayOfWeek(LocalDate date, DayOfWeek firstDayOfWeek) {
        LocalDate startOfWeek = adjustToFirstDayOfWeek(date, firstDayOfWeek);
        return startOfWeek.plusDays(6);
    }

    /**
     * Creates a bidirectional binding between the two given properties of different types via the
     * help of a {@link Converter}.
     *
     * @param leftProperty  the left property
     * @param rightProperty the right property
     * @param converter     the converter
     * @param            the type of the left property
     * @param            the type of the right property
     */
    public static  void bindBidirectional(Property leftProperty, Property rightProperty, Converter converter) {
        BidirectionalConversionBinding binding = new BidirectionalConversionBinding<>(leftProperty, rightProperty, converter);
        leftProperty.addListener(binding);
        rightProperty.addListener(binding);
        leftProperty.setValue(converter.toLeft(rightProperty.getValue()));
    }

    private static class BidirectionalConversionBinding implements InvalidationListener, WeakListener {

        private WeakReference> leftReference;
        private WeakReference> rightReference;
        private Converter converter;
        private boolean updating;

        private BidirectionalConversionBinding(Property leftProperty, Property rightProperty, Converter converter) {
            this.leftReference = new WeakReference<>(Objects.requireNonNull(leftProperty));
            this.rightReference = new WeakReference<>(Objects.requireNonNull(rightProperty));
            this.converter = Objects.requireNonNull(converter);
        }

        public Property getLeftProperty() {
            return leftReference.get();
        }

        public Property getRightProperty() {
            return rightReference.get();
        }

        @Override
        public boolean wasGarbageCollected() {
            return getLeftProperty() == null || getRightProperty() == null;
        }

        @Override
        public void invalidated(Observable observable) {
            if (updating) {
                return;
            }

            final Property leftProperty = getLeftProperty();
            final Property rightProperty = getRightProperty();

            if (wasGarbageCollected()) {
                if (leftProperty != null) {
                    leftProperty.removeListener(this);
                }
                if (rightProperty != null) {
                    rightProperty.removeListener(this);
                }
            } else {
                try {
                    updating = true;

                    if (observable == leftProperty) {
                        rightProperty.setValue(converter.toRight(leftProperty.getValue()));
                    } else {
                        leftProperty.setValue(converter.toLeft(rightProperty.getValue()));
                    }
                } finally {
                    updating = false;
                }
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy