/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (SpanOfWeekdays.java) is part of project Time4J.
*
* Time4J is free software: You can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* Time4J 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Time4J. If not, see <http://www.gnu.org/licenses/>.
* -----------------------------------------------------------------------
*/
package net.time4j.range;
import net.time4j.PlainDate;
import net.time4j.Weekday;
import net.time4j.base.TimeSource;
import net.time4j.engine.AttributeQuery;
import net.time4j.engine.BasicElement;
import net.time4j.engine.ChronoDisplay;
import net.time4j.engine.ChronoElement;
import net.time4j.engine.ChronoEntity;
import net.time4j.engine.ChronoException;
import net.time4j.engine.ChronoMerger;
import net.time4j.engine.Chronology;
import net.time4j.engine.ElementRule;
import net.time4j.engine.FormattableElement;
import net.time4j.format.Attributes;
import net.time4j.format.CalendarType;
import net.time4j.format.OutputContext;
import net.time4j.format.TextElement;
import net.time4j.format.TextWidth;
import net.time4j.format.expert.ChronoFormatter;
import net.time4j.format.expert.PatternType;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.io.StreamCorruptedException;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
/**
* <p>Describes an arbitrary span of weekdays. </p>
*
* <p>Following elements which are declared as constants are registered by
* this class: </p>
*
* <ul>
* <li>{@link #START}</li>
* <li>{@link #END}</li>
* </ul>
*
* @author Meno Hochschild
* @since 4.20
* @doctags.concurrency {immutable}
*/
/*[deutsch]
* <p>Beschreibt eine beliebige Spanne von Wochentagen. </p>
*
* <p>Registriert sind folgende als Konstanten deklarierte Elemente: </p>
*
* <ul>
* <li>{@link #START}</li>
* <li>{@link #END}</li>
* </ul>
*
* @author Meno Hochschild
* @since 4.20
* @doctags.concurrency {immutable}
*/
@CalendarType("iso8601")
public final class SpanOfWeekdays
extends ChronoEntity<SpanOfWeekdays>
implements Iterable<Weekday>, Serializable {
//~ Statische Felder/Initialisierungen --------------------------------
/**
* <p>Denotes the start of this span of weekdays. </p>
*/
/*[deutsch]
* <p>Bezeichnet den Anfang dieser Wochentagsspanne. </p>
*/
@FormattableElement(format = "S")
public static final ChronoElement<Weekday> START;
/**
* <p>Denotes the end of this span of weekdays (inclusive). </p>
*/
/*[deutsch]
* <p>Bezeichnet das Ende dieser Wochentagsspanne. </p>
*/
@FormattableElement(format = "E")
public static final ChronoElement<Weekday> END;
private static final Chronology<SpanOfWeekdays> ENGINE;
static {
Element s = new Element("START", 'S');
Element e = new Element("END", 'E');
START = s;
END = e;
ENGINE =
Chronology.Builder.setUp(SpanOfWeekdays.class, new Merger())
.appendElement(START, s)
.appendElement(END, e)
.build();
}
private static final SpanOfWeekdays MONDAY_TO_FRIDAY = new SpanOfWeekdays(Weekday.MONDAY, Weekday.FRIDAY);
private static final long serialVersionUID = 3484703887286756207L;
//~ Instanzvariablen --------------------------------------------------
/**
* @serial the start of the span of weekdays
*/
/*[deutsch]
* @serial der Start der Wochentagsspanne
*/
private final Weekday start;
/**
* @serial the end of the span of weekdays
*/
/*[deutsch]
* @serial das Ende der Wochentagsspanne
*/
private final Weekday end;
//~ Konstruktoren -----------------------------------------------------
private SpanOfWeekdays(
Weekday start,
Weekday end
) {
super();
this.start = start;
this.end = end;
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Creates a span of weekdays of only one day. </p>
*
* @param day the single day of week which forms the span
* @return span of weekdays consisting of only given day
*/
/*[deutsch]
* <p>Erzeugt eine Wochentagsspanne, die aus nur einem Tag besteht. </p>
*
* @param day the single day of week which forms the span
* @return span of weekdays consisting of only given day
*/
public static SpanOfWeekdays on(Weekday day) {
return between(day, day);
}
/**
* <p>Creates a typical working week from Monday to Friday. </p>
*
* @return span of weekdays from Monday to Friday
*/
/*[deutsch]
* <p>Erzeugt eine typische Arbeitswoche von Montag bis Freitag. </p>
*
* @return span of weekdays from Monday to Friday
*/
public static SpanOfWeekdays betweenMondayAndFriday() {
return MONDAY_TO_FRIDAY;
}
/**
* <p>Creates a new span of weekdays. </p>
*
* <p>It is possible to choose the same weekday for start and end. Then the resulting span will
* just consist of one single weekday. </p>
*
* @param start the starting weekday
* @param end the ending weekday (inclusive)
* @return new span of weekdays
*/
/*[deutsch]
* <p>Erzeugt eine neue Spanne von Wochentagen. </p>
*
* <p>Es ist möglich, denselben Wochentag für Start und Ende zu wählen. In diesem
* Fall besteht die Spanne aus genau einem Wochentag. </p>
*
* @param start the starting weekday
* @param end the ending weekday (inclusive)
* @return new span of weekdays
*/
public static SpanOfWeekdays between(
Weekday start,
Weekday end
) {
if (start == null || end == null) {
throw new NullPointerException("Missing day of week.");
}
return new SpanOfWeekdays(start, end);
}
/**
* <p>Obtains the start of this span of weekdays. </p>
*
* @return the starting weekday
*/
/*[deutsch]
* <p>Liefert den Start dieser Wochentagsspanne. </p>
*
* @return the starting weekday
*/
public Weekday getStart() {
return this.start;
}
/**
* <p>Obtains the end of this span of weekdays. </p>
*
* @return the ending weekday
*/
/*[deutsch]
* <p>Liefert das Ende dieser Wochentagsspanne. </p>
*
* @return the ending weekday
*/
public Weekday getEnd() {
return this.end;
}
/**
* <p>Determines the count of days belonging to this span of weekdays. </p>
*
* @return count of days in range {@code 1-7}
*/
/**
* <p>Ermittelt die Anzahl der Tage, die zu dieser Wochentagsspanne gehören. </p>
*
* @return count of days in range {@code 1-7}
*/
public int length() {
int days = 1;
Weekday current = this.start;
while (current != this.end) {
days++;
current = current.next();
}
return days;
}
@Override
public Iterator<Weekday> iterator() {
List<Weekday> days = new ArrayList<>(7);
days.add(this.start);
Weekday current = this.start;
while (current != this.end) {
current = current.next();
days.add(current);
}
return days.iterator();
}
/**
* <p>Creates a formatter for given dynamic format pattern and locale. </p>
*
* <p>The pattern is of {@link PatternType#DYNAMIC dynamic} type and only uses the
* symbol letters "S" (=START) and "E" (=END). The start must
* be present, but the end is optional. If the end is missing in parsing then it
* will be set to the start. The count of symbols controls the text width, and the
* output context can be set by an extra format attribute. Example: </p>
*
* <pre>
* ChronoFormatter<SpanOfWeekdays> f =
* SpanOfWeekdays.formatter("SSSS[ 'to' EEEE]", Locale.ENGLISH);
*
* assertThat(f.format(SpanOfWeekdays.betweenMondayAndFriday()), is("Monday to Friday"));
* assertThat(f.parse("Sunday"), is(SpanOfWeekdays.on(Weekday.SUNDAY)));
* </pre>
*
* @param dynamicPattern format pattern
* @param locale the locale information
* @return new formatter
* @see #START
* @see #END
* @see PatternType#DYNAMIC
* @see Attributes#OUTPUT_CONTEXT
*/
/*[deutsch]
* <p>Erzeugt einen Formatierer für das angegebene dynamische Formatmuster und die Sprache. </p>
*
* <p>Das Formatmuster ist {@link PatternType#DYNAMIC dynamisch} und nutzt nur die
* Symbole "S" (=START) und "E" (=ENDE). Der Start muß immer
* vorhanden sein, aber das Ende darf fehlen. Wenn das Ende beim Interpretieren fehlt,
* wird es auf den Start gesetzt. Die Anzahl der Symbole steuert die Textbreite, und
* der Ausgabekontext kann mit Hilfe eines extra Formatattributs gesetzt werden. Beispiel: </p>
*
* <pre>
* ChronoFormatter<SpanOfWeekdays> f =
* SpanOfWeekdays.formatter("SSSS[ 'bis' EEEE]", Locale.GERMAN);
*
* assertThat(f.format(SpanOfWeekdays.betweenMondayAndFriday()), is("Montag bis Freitag"));
* assertThat(f.parse("Sonntag"), is(SpanOfWeekdays.on(Weekday.SUNDAY)));
* </pre>
*
* @param dynamicPattern format pattern
* @param locale the locale information
* @return new formatter
* @see #START
* @see #END
* @see PatternType#DYNAMIC
* @see Attributes#OUTPUT_CONTEXT
*/
public static ChronoFormatter<SpanOfWeekdays> formatter(
String dynamicPattern,
Locale locale
) {
return ChronoFormatter
.ofPattern(dynamicPattern, PatternType.DYNAMIC, locale, ENGINE)
.withDefaultSource(END, START);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof SpanOfWeekdays) {
SpanOfWeekdays that = (SpanOfWeekdays) obj;
return ((this.start == that.start) && (this.end == that.end));
} else {
return false;
}
}
@Override
public int hashCode() {
return this.start.hashCode() ^ this.end.hashCode();
}
@Override
public String toString() {
return this.start + "-" + this.end;
}
/**
* <p>Yields the associated chronology. </p>
*
* @return the underlying rule engine
*/
/*[deutsch]
* <p>Liefert die assoziierte Chronologie. </p>
*
* @return the underlying rule engine
*/
public static Chronology<SpanOfWeekdays> chronology() {
return ENGINE;
}
@Override
protected Chronology<SpanOfWeekdays> getChronology() {
return ENGINE;
}
@Override
protected SpanOfWeekdays getContext() {
return this;
}
//~ Innere Klassen ----------------------------------------------------
private static class Element
extends BasicElement<Weekday>
implements TextElement<Weekday>, ElementRule<SpanOfWeekdays, Weekday> {
//~ Statische Felder/Initialisierungen ----------------------------
private static final long serialVersionUID = 8221317125139231996L;
//~ Instanzvariablen ----------------------------------------------
private transient final char symbol;
//~ Konstruktoren -------------------------------------------------
Element(
String name,
char symbol
) {
super(name);
this.symbol = symbol;
}
//~ Methoden ------------------------------------------------------
@Override
public char getSymbol() {
return this.symbol;
}
@Override
public void print(
ChronoDisplay context,
Appendable buffer,
AttributeQuery attributes
) throws IOException, ChronoException {
Locale locale = attributes.get(Attributes.LANGUAGE, Locale.ROOT);
TextWidth textWidth = attributes.get(Attributes.TEXT_WIDTH, TextWidth.WIDE);
OutputContext outputContext = attributes.get(Attributes.OUTPUT_CONTEXT, OutputContext.FORMAT);
buffer.append(context.get(this).getDisplayName(locale, textWidth, outputContext));
}
@Override
public Weekday parse(
CharSequence text,
ParsePosition status,
AttributeQuery attributes
) {
TextElement<?> e = TextElement.class.cast(PlainDate.DAY_OF_WEEK);
return Weekday.class.cast(e.parse(text, status, attributes));
}
@Override
public Class<Weekday> getType() {
return Weekday.class;
}
@Override
public Weekday getDefaultMinimum() {
return Weekday.MONDAY; // ISO-8601
}
@Override
public Weekday getDefaultMaximum() {
return Weekday.SUNDAY; // ISO-8601
}
@Override
public boolean isDateElement() {
return false; // a span of weekdays is not a date
}
@Override
public boolean isTimeElement() {
return false;
}
@Override
protected boolean isSingleton() {
return true;
}
private Object readResolve() throws ObjectStreamException {
switch (this.name()) {
case "START":
return START;
case "END":
return END;
default:
throw new StreamCorruptedException();
}
}
@Override
public Weekday getValue(SpanOfWeekdays context) {
return ((this.symbol == 'S') ? context.start : context.end);
}
@Override
public Weekday getMinimum(SpanOfWeekdays context) {
if (this.symbol == 'S') {
return context.end.next();
} else {
return context.start;
}
}
@Override
public Weekday getMaximum(SpanOfWeekdays context) {
if (this.symbol == 'S') {
return context.end;
} else {
return context.start.previous();
}
}
@Override
public boolean isValid(
SpanOfWeekdays context,
Weekday value
) {
return (value != null);
}
@Override
public SpanOfWeekdays withValue(
SpanOfWeekdays context,
Weekday value,
boolean lenient
) {
if (this.symbol == 'S') {
return SpanOfWeekdays.between(value, context.end);
} else {
return SpanOfWeekdays.between(context.start, value);
}
}
@Override
public ChronoElement<?> getChildAtFloor(SpanOfWeekdays context) {
return null;
}
@Override
public ChronoElement<?> getChildAtCeiling(SpanOfWeekdays context) {
return null;
}
}
private static class Merger
implements ChronoMerger<SpanOfWeekdays> {
//~ Methoden ------------------------------------------------------
@Override
public SpanOfWeekdays createFrom(
TimeSource<?> clock,
AttributeQuery attributes
) {
PlainDate date = PlainDate.axis().createFrom(clock, attributes);
if (date == null) {
return null;
} else {
Weekday dow = date.getDayOfWeek();
return SpanOfWeekdays.between(dow, dow);
}
}
@Override
public SpanOfWeekdays createFrom(
ChronoEntity<?> entity,
AttributeQuery attributes,
boolean preparsing
) {
if (entity.contains(START) && entity.contains(END)) {
return SpanOfWeekdays.between(entity.get(START), entity.get(END));
} else {
return null;
}
}
}
}