fixes for Sonarqube

This commit is contained in:
Jörg Prante 2016-11-02 16:40:54 +01:00
parent 2aead18a0f
commit 1d11478360
48 changed files with 1475 additions and 1175 deletions

18
CREDITS.txt Normal file
View file

@ -0,0 +1,18 @@
org.xbib.time is based upon the following software:
- Chronic https://github.com/samtingleff/jchronic (MIT License)
- org.joda.time.format https://github.com/JodaOrg/joda-time (Apache 2.0)
- prettytime https://github.com/ocpsoft/prettytime (Apache 2.0)
with improvements by Jörg Prante including
- converted to Java 8 java.time API
- fixing bugs reported by Sonarqube
- refactoring classes
- rewritten code to simplify and ease development

View file

@ -3,17 +3,26 @@ package org.xbib.time.chronic;
import org.xbib.time.chronic.handlers.Handler; import org.xbib.time.chronic.handlers.Handler;
import org.xbib.time.chronic.numerizer.Numerizer; import org.xbib.time.chronic.numerizer.Numerizer;
import org.xbib.time.chronic.repeaters.Repeater; import org.xbib.time.chronic.repeaters.Repeater;
import org.xbib.time.chronic.tags.*; import org.xbib.time.chronic.tags.Grabber;
import org.xbib.time.chronic.tags.Ordinal;
import org.xbib.time.chronic.tags.Pointer;
import org.xbib.time.chronic.tags.Scalar;
import org.xbib.time.chronic.tags.Separator;
import org.xbib.time.chronic.tags.TimeZone;
import java.text.ParseException; import java.text.ParseException;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* *
*/ */
public class Chronic { public class Chronic {
private static final Logger logger = Logger.getLogger(Chronic.class.getName());
private Chronic() { private Chronic() {
} }
@ -74,7 +83,8 @@ public class Chronic {
try { try {
tokens = (List<Token>) optionScannerClass.getMethod("scan", List.class, Options.class) tokens = (List<Token>) optionScannerClass.getMethod("scan", List.class, Options.class)
.invoke(null, tokens, options); .invoke(null, tokens, options);
} catch (Throwable e) { } catch (Exception e) {
logger.log(Level.FINE, e.getMessage(), e);
throw new ParseException("failed to scan tokens", 0); throw new ParseException("failed to scan tokens", 0);
} }
} }
@ -89,7 +99,8 @@ public class Chronic {
try { try {
tokens = (List<Token>) scannerClass.getMethod("scan", List.class, Options.class) tokens = (List<Token>) scannerClass.getMethod("scan", List.class, Options.class)
.invoke(null, tokens, options); .invoke(null, tokens, options);
} catch (Throwable e) { } catch (Exception e) {
logger.log(Level.FINE, e.getMessage(), e);
throw new ParseException("failed to scan tokens", 0); throw new ParseException("failed to scan tokens", 0);
} }
} }

View file

@ -46,6 +46,19 @@ public class Span extends Range {
return zoneId; return zoneId;
} }
@Override
public int hashCode() {
return super.hashCode() ^ zoneId.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof Span &&
((Span) obj).getBegin().equals(getBegin()) &&
((Span) obj).getEnd().equals(getEnd()) &&
((Span) obj).zoneId.equals(zoneId);
}
@Override @Override
public String toString() { public String toString() {
return "(" + DateTimeFormatter.ISO_INSTANT.format(getBeginCalendar()) return "(" + DateTimeFormatter.ISO_INSTANT.format(getBeginCalendar())

View file

@ -62,7 +62,7 @@ public class Token {
public <T extends Tag<?>> T getTag(Class<T> tagClass) { public <T extends Tag<?>> T getTag(Class<T> tagClass) {
List<T> matches = getTags(tagClass); List<T> matches = getTags(tagClass);
T matchingTag = null; T matchingTag = null;
if (matches.size() > 0) { if (!matches.isEmpty()) {
matchingTag = matches.get(0); matchingTag = matches.get(0);
} }
return matchingTag; return matchingTag;

View file

@ -30,11 +30,7 @@ import org.xbib.time.chronic.tags.TimeZone;
import java.text.ParseException; import java.text.ParseException;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.Collections; import java.util.*;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/** /**
* *
@ -57,7 +53,7 @@ public class Handler {
public static synchronized Map<HandlerType, List<Handler>> definitions() { public static synchronized Map<HandlerType, List<Handler>> definitions() {
if (definitions == null) { if (definitions == null) {
Map<HandlerType, List<Handler>> definitions = new HashMap<>(); Map<HandlerType, List<Handler>> definitions = new EnumMap<>(HandlerType.class);
List<Handler> timeHandlers = new LinkedList<>(); List<Handler> timeHandlers = new LinkedList<>();
timeHandlers.add(new Handler(null, new TagPattern(RepeaterTime.class), timeHandlers.add(new Handler(null, new TagPattern(RepeaterTime.class),
@ -227,7 +223,7 @@ public class Handler {
if (grabberType == Grabber.Relative.LAST) { if (grabberType == Grabber.Relative.LAST) {
outerSpan = head.nextSpan(PointerType.PAST); outerSpan = head.nextSpan(PointerType.PAST);
} else if (grabberType == Grabber.Relative.THIS) { } else if (grabberType == Grabber.Relative.THIS) {
if (repeaters.size() > 0) { if (!repeaters.isEmpty()) {
outerSpan = head.thisSpan(PointerType.NONE); outerSpan = head.thisSpan(PointerType.NONE);
} else { } else {
outerSpan = head.thisSpan(options.getContext()); outerSpan = head.thisSpan(options.getContext());
@ -273,11 +269,11 @@ public class Handler {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static List<Token> dealiasAndDisambiguateTimes(List<Token> tokens, Options options) { public static List<Token> dealiasAndDisambiguateTimes(List<Token> tokenList, Options options) {
// handle aliases of am/pm // handle aliases of am/pm
// 5:00 in the morning => 5:00 am // 5:00 in the morning => 5:00 am
// 7:00 in the evening => 7:00 pm // 7:00 in the evening => 7:00 pm
List<Token> tokens = tokenList;
int dayPortionIndex = -1; int dayPortionIndex = -1;
int tokenSize = tokens.size(); int tokenSize = tokens.size();
for (int i = 0; dayPortionIndex == -1 && i < tokenSize; i++) { for (int i = 0; dayPortionIndex == -1 && i < tokenSize; i++) {
@ -347,8 +343,8 @@ public class Handler {
for (HandlerPattern pattern : patterns) { for (HandlerPattern pattern : patterns) {
boolean optional = pattern.isOptional(); boolean optional = pattern.isOptional();
if (pattern instanceof TagPattern) { if (pattern instanceof TagPattern) {
boolean match = (tokenIndex < tokens.size() && boolean match = tokenIndex < tokens.size() &&
tokens.get(tokenIndex).getTags(((TagPattern) pattern).getTagClass()).size() > 0); tokens.get(tokenIndex).getTags(((TagPattern) pattern).getTagClass()).size() > 0;
if (!match && !optional) { if (!match && !optional) {
return false; return false;
} }

View file

@ -9,6 +9,7 @@ import java.util.List;
/** /**
* *
*/ */
@FunctionalInterface
public interface IHandler { public interface IHandler {
Span handle(List<Token> tokens, Options options); Span handle(List<Token> tokens, Options options);
} }

View file

@ -11,6 +11,7 @@ import java.util.List;
*/ */
public class ORGRHandler extends ORRHandler { public class ORGRHandler extends ORRHandler {
@Override
public Span handle(List<Token> tokens, Options options) { public Span handle(List<Token> tokens, Options options) {
Span outerSpan = Handler.getAnchor(tokens.subList(2, 4), options); Span outerSpan = Handler.getAnchor(tokens.subList(2, 4), options);
return handle(tokens.subList(0, 2), outerSpan, options); return handle(tokens.subList(0, 2), outerSpan, options);

View file

@ -11,6 +11,7 @@ import java.util.List;
*/ */
public class ORSRHandler extends ORRHandler { public class ORSRHandler extends ORRHandler {
@Override
public Span handle(List<Token> tokens, Options options) { public Span handle(List<Token> tokens, Options options) {
Span outerSpan = Handler.getAnchor(tokens.subList(3, 4), options); Span outerSpan = Handler.getAnchor(tokens.subList(3, 4), options);
return handle(tokens.subList(0, 2), outerSpan, options); return handle(tokens.subList(0, 2), outerSpan, options);

View file

@ -11,6 +11,7 @@ import java.util.List;
*/ */
public class RHandler implements IHandler { public class RHandler implements IHandler {
@Override
public Span handle(List<Token> tokens, Options options) { public Span handle(List<Token> tokens, Options options) {
List<Token> ddTokens = Handler.dealiasAndDisambiguateTimes(tokens, options); List<Token> ddTokens = Handler.dealiasAndDisambiguateTimes(tokens, options);
return Handler.getAnchor(ddTokens, options); return Handler.getAnchor(ddTokens, options);

View file

@ -9,12 +9,17 @@ import org.xbib.time.chronic.tags.ScalarYear;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* *
*/ */
public class RdnRmnSdTTzSyHandler implements IHandler { public class RdnRmnSdTTzSyHandler implements IHandler {
private static final Logger logger = Logger.getLogger(RdnRmnSdTTzSyHandler.class.getName());
@Override
public Span handle(List<Token> tokens, Options options) { public Span handle(List<Token> tokens, Options options) {
int month = tokens.get(1).getTag(RepeaterMonthName.class).getType().ordinal(); int month = tokens.get(1).getTag(RepeaterMonthName.class).getType().ordinal();
int day = tokens.get(2).getTag(ScalarDay.class).getType(); int day = tokens.get(2).getTag(ScalarDay.class).getType();
@ -25,6 +30,7 @@ public class RdnRmnSdTTzSyHandler implements IHandler {
ZonedDateTime dayStart = ZonedDateTime.of(year, month, day, 0, 0, 0, 0, options.getZoneId()); ZonedDateTime dayStart = ZonedDateTime.of(year, month, day, 0, 0, 0, 0, options.getZoneId());
span = Handler.dayOrTime(dayStart, timeTokens, options); span = Handler.dayOrTime(dayStart, timeTokens, options);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
logger.log(Level.FINE, e.getMessage(), e);
span = null; span = null;
} }
return span; return span;

View file

@ -12,6 +12,8 @@ import java.util.List;
* *
*/ */
public class RmnOdHandler extends MDHandler { public class RmnOdHandler extends MDHandler {
@Override
public Span handle(List<Token> tokens, Options options) { public Span handle(List<Token> tokens, Options options) {
return handle(tokens.get(0).getTag(RepeaterMonthName.class), return handle(tokens.get(0).getTag(RepeaterMonthName.class),
tokens.get(1).getTag(OrdinalDay.class), tokens.subList(2, tokens.size()), options); tokens.get(1).getTag(OrdinalDay.class), tokens.subList(2, tokens.size()), options);

View file

@ -12,6 +12,8 @@ import java.util.List;
* *
*/ */
public class RmnSdHandler extends MDHandler { public class RmnSdHandler extends MDHandler {
@Override
public Span handle(List<Token> tokens, Options options) { public Span handle(List<Token> tokens, Options options) {
return handle(tokens.get(0).getTag(RepeaterMonthName.class), return handle(tokens.get(0).getTag(RepeaterMonthName.class),
tokens.get(1).getTag(ScalarDay.class), tokens.subList(2, tokens.size()), options); tokens.get(1).getTag(ScalarDay.class), tokens.subList(2, tokens.size()), options);

View file

@ -9,12 +9,17 @@ import org.xbib.time.chronic.tags.ScalarYear;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* *
*/ */
public class RmnSdSyHandler implements IHandler { public class RmnSdSyHandler implements IHandler {
private static final Logger logger = Logger.getLogger(RmnSdSyHandler.class.getName());
@Override
public Span handle(List<Token> tokens, Options options) { public Span handle(List<Token> tokens, Options options) {
int month = tokens.get(0).getTag(RepeaterMonthName.class).getType().ordinal(); int month = tokens.get(0).getTag(RepeaterMonthName.class).getType().ordinal();
int day = tokens.get(1).getTag(ScalarDay.class).getType(); int day = tokens.get(1).getTag(ScalarDay.class).getType();
@ -25,6 +30,7 @@ public class RmnSdSyHandler implements IHandler {
ZonedDateTime dayStart = ZonedDateTime.of(year, month, day, 0, 0, 0, 0, options.getZoneId()); ZonedDateTime dayStart = ZonedDateTime.of(year, month, day, 0, 0, 0, 0, options.getZoneId());
span = Handler.dayOrTime(dayStart, timeTokens, options); span = Handler.dayOrTime(dayStart, timeTokens, options);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
logger.log(Level.FINE, e.getMessage(), e);
span = null; span = null;
} }
return span; return span;

View file

@ -9,12 +9,17 @@ import org.xbib.time.chronic.tags.ScalarYear;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* *
*/ */
public class RmnSyHandler implements IHandler { public class RmnSyHandler implements IHandler {
private static final Logger logger = Logger.getLogger(RmnSyHandler.class.getName());
@Override
public Span handle(List<Token> tokens, Options options) { public Span handle(List<Token> tokens, Options options) {
int month = tokens.get(0).getTag(RepeaterMonthName.class).getType().ordinal(); int month = tokens.get(0).getTag(RepeaterMonthName.class).getType().ordinal();
int year = tokens.get(1).getTag(ScalarYear.class).getType(); int year = tokens.get(1).getTag(ScalarYear.class).getType();
@ -24,6 +29,7 @@ public class RmnSyHandler implements IHandler {
ZonedDateTime end = start.plus(1, ChronoUnit.MONTHS); ZonedDateTime end = start.plus(1, ChronoUnit.MONTHS);
span = new Span(start, end); span = new Span(start, end);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
logger.log(Level.FINE, e.getMessage(), e);
span = null; span = null;
} }
return span; return span;

View file

@ -14,6 +14,8 @@ import java.util.List;
* *
*/ */
public class SmSdHandler implements IHandler { public class SmSdHandler implements IHandler {
@Override
public Span handle(List<Token> tokens, Options options) { public Span handle(List<Token> tokens, Options options) {
int month = tokens.get(0).getTag(ScalarMonth.class).getType(); int month = tokens.get(0).getTag(ScalarMonth.class).getType();
int day = tokens.get(1).getTag(ScalarDay.class).getType(); int day = tokens.get(1).getTag(ScalarDay.class).getType();

View file

@ -9,12 +9,17 @@ import org.xbib.time.chronic.tags.ScalarYear;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* *
*/ */
public class SmSdSyHandler implements IHandler { public class SmSdSyHandler implements IHandler {
private static final Logger logger = Logger.getLogger(SmSdSyHandler.class.getName());
@Override
public Span handle(List<Token> tokens, Options options) { public Span handle(List<Token> tokens, Options options) {
int month = tokens.get(0).getTag(ScalarMonth.class).getType(); int month = tokens.get(0).getTag(ScalarMonth.class).getType();
int day = tokens.get(1).getTag(ScalarDay.class).getType(); int day = tokens.get(1).getTag(ScalarDay.class).getType();
@ -25,6 +30,7 @@ public class SmSdSyHandler implements IHandler {
ZonedDateTime dayStart = ZonedDateTime.of(year, month, day, 0, 0, 0, 0, options.getZoneId()); ZonedDateTime dayStart = ZonedDateTime.of(year, month, day, 0, 0, 0, 0, options.getZoneId());
span = Handler.dayOrTime(dayStart, timeTokens, options); span = Handler.dayOrTime(dayStart, timeTokens, options);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
logger.log(Level.FINE, e.getMessage(), e);
span = null; span = null;
} }
return span; return span;

View file

@ -9,12 +9,17 @@ import org.xbib.time.chronic.tags.ScalarYear;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* *
*/ */
public class SmSyHandler implements IHandler { public class SmSyHandler implements IHandler {
private static final Logger logger = Logger.getLogger(SmSyHandler.class.getName());
@Override
public Span handle(List<Token> tokens, Options options) { public Span handle(List<Token> tokens, Options options) {
int month = tokens.get(0).getTag(ScalarMonth.class).getType(); int month = tokens.get(0).getTag(ScalarMonth.class).getType();
int year = tokens.get(1).getTag(ScalarYear.class).getType(); int year = tokens.get(1).getTag(ScalarYear.class).getType();
@ -24,6 +29,7 @@ public class SmSyHandler implements IHandler {
ZonedDateTime end = start.plus(1, ChronoUnit.MONTHS); ZonedDateTime end = start.plus(1, ChronoUnit.MONTHS);
span = new Span(start, end); span = new Span(start, end);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
logger.log(Level.FINE, e.getMessage(), e);
span = null; span = null;
} }
return span; return span;

View file

@ -125,10 +125,10 @@ public class Numerizer {
} }
private static String andition(String str) { private static String andition(String str) {
StringBuffer anditionStr = new StringBuffer(str); StringBuilder anditionStr = new StringBuilder(str);
Matcher matcher = Numerizer.ANDITION_PATTERN.matcher(anditionStr); Matcher matcher = Numerizer.ANDITION_PATTERN.matcher(anditionStr);
while (matcher.find()) { while (matcher.find()) {
if (matcher.group(2).equalsIgnoreCase(" and ") || matcher.group(1).length() > matcher.group(3).length()) { if (" and ".equalsIgnoreCase(matcher.group(2)) || matcher.group(1).length() > matcher.group(3).length()) {
anditionStr.replace(matcher.start(), matcher.end(), anditionStr.replace(matcher.start(), matcher.end(),
String.valueOf(Integer.parseInt(matcher.group(1).trim()) + String.valueOf(Integer.parseInt(matcher.group(1).trim()) +
Integer.parseInt(matcher.group(3).trim()))); Integer.parseInt(matcher.group(3).trim())));
@ -141,7 +141,7 @@ public class Numerizer {
/** /**
* *
*/ */
static class DirectNum { private static class DirectNum {
private Pattern name; private Pattern name;
private String number; private String number;

View file

@ -49,6 +49,7 @@ public abstract class Repeater<T> extends Tag<T> implements Comparable<Repeater<
return tokens; return tokens;
} }
@Override
public int compareTo(Repeater<?> other) { public int compareTo(Repeater<?> other) {
return Integer.compare(getWidth(), other.getWidth()); return Integer.compare(getWidth(), other.getWidth());
} }
@ -84,6 +85,18 @@ public abstract class Repeater<T> extends Tag<T> implements Comparable<Repeater<
public abstract Span getOffset(Span span, int amount, Pointer.PointerType pointer); public abstract Span getOffset(Span span, int amount, Pointer.PointerType pointer);
@Override
public int hashCode() {
return getType().hashCode() ^ getNow().hashCode();
}
@Override
public boolean equals(Object other) {
return other instanceof Repeater &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
@Override @Override
public String toString() { public String toString() {
return "repeater"; return "repeater";

View file

@ -64,6 +64,18 @@ public class RepeaterDay extends RepeaterUnit {
return RepeaterDay.DAY_SECONDS; return RepeaterDay.DAY_SECONDS;
} }
@Override
public int hashCode() {
return super.hashCode() ^ getWidth();
}
@Override
public boolean equals(Object other) {
return other instanceof RepeaterDay &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
@Override @Override
public String toString() { public String toString() {
return super.toString() + "-day"; return super.toString() + "-day";

View file

@ -15,7 +15,7 @@ import java.util.regex.Pattern;
* *
*/ */
public class RepeaterDayName extends Repeater<RepeaterDayName.DayName> { public class RepeaterDayName extends Repeater<RepeaterDayName.DayName> {
public static final int DAY_SECONDS = 86400; // (24 * 60 * 60); private static final int DAY_SECONDS = 86400;
private static final Pattern MON_PATTERN = Pattern.compile("^m[ou]n(day)?$"); private static final Pattern MON_PATTERN = Pattern.compile("^m[ou]n(day)?$");
private static final Pattern TUE_PATTERN = Pattern.compile("^t(ue|eu|oo|u|)s(day)?$"); private static final Pattern TUE_PATTERN = Pattern.compile("^t(ue|eu|oo|u|)s(day)?$");
private static final Pattern TUE_PATTERN_1 = Pattern.compile("^tue$"); private static final Pattern TUE_PATTERN_1 = Pattern.compile("^tue$");
@ -69,17 +69,14 @@ public class RepeaterDayName extends Repeater<RepeaterDayName.DayName> {
currentDayStart = currentDayStart.plus(direction, ChronoUnit.DAYS); currentDayStart = currentDayStart.plus(direction, ChronoUnit.DAYS);
} }
} else { } else {
currentDayStart = currentDayStart.plus(direction * 7, ChronoUnit.DAYS); currentDayStart = currentDayStart.plus(direction * 7L, ChronoUnit.DAYS);
} }
return new Span(currentDayStart, ChronoUnit.DAYS, 1); return new Span(currentDayStart, ChronoUnit.DAYS, 1);
} }
@Override @Override
protected Span internalThisSpan(PointerType pointer) { protected Span internalThisSpan(PointerType pointer) {
if (pointer == PointerType.NONE) { return super.nextSpan(pointer == PointerType.NONE ? PointerType.FUTURE : pointer);
pointer = PointerType.FUTURE;
}
return super.nextSpan(pointer);
} }
@Override @Override
@ -92,6 +89,18 @@ public class RepeaterDayName extends Repeater<RepeaterDayName.DayName> {
return RepeaterDayName.DAY_SECONDS; return RepeaterDayName.DAY_SECONDS;
} }
@Override
public int hashCode() {
return super.hashCode() ^ getWidth();
}
@Override
public boolean equals(Object other) {
return other instanceof RepeaterDayName &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
@Override @Override
public String toString() { public String toString() {
return super.toString() + "-dayname-" + getType(); return super.toString() + "-dayname-" + getType();

View file

@ -58,7 +58,6 @@ public abstract class RepeaterDayPortion<T> extends Repeater<T> {
@Override @Override
protected Span internalNextSpan(PointerType pointer) { protected Span internalNextSpan(PointerType pointer) {
ZonedDateTime rangeStart; ZonedDateTime rangeStart;
ZonedDateTime rangeEnd;
if (currentSpan == null) { if (currentSpan == null) {
long nowSeconds = getNow().toInstant().getEpochSecond() - ymd(getNow()).toInstant().getEpochSecond(); long nowSeconds = getNow().toInstant().getEpochSecond() - ymd(getNow()).toInstant().getEpochSecond();
if (nowSeconds < range.getBegin()) { if (nowSeconds < range.getBegin()) {
@ -133,6 +132,18 @@ public abstract class RepeaterDayPortion<T> extends Repeater<T> {
protected abstract Range createRange(T type); protected abstract Range createRange(T type);
@Override
public int hashCode() {
return super.hashCode() ^ getWidth();
}
@Override
public boolean equals(Object other) {
return other instanceof RepeaterDayPortion &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
@Override @Override
public String toString() { public String toString() {
return super.toString() + "-dayportion-" + getType(); return super.toString() + "-dayportion-" + getType();

View file

@ -35,7 +35,7 @@ public class RepeaterFortnight extends RepeaterUnit {
Span lastSundaySpan = sundayRepeater.nextSpan(PointerType.PAST); Span lastSundaySpan = sundayRepeater.nextSpan(PointerType.PAST);
currentFortnightStart = lastSundaySpan.getBeginCalendar(); currentFortnightStart = lastSundaySpan.getBeginCalendar();
} else { } else {
throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); throw new IllegalArgumentException("Unable to handle pointer " + pointer);
} }
} else { } else {
long direction = (pointer == PointerType.FUTURE) ? 1L : -1L; long direction = (pointer == PointerType.FUTURE) ? 1L : -1L;
@ -47,11 +47,11 @@ public class RepeaterFortnight extends RepeaterUnit {
} }
@Override @Override
protected Span internalThisSpan(PointerType pointer) { protected Span internalThisSpan(PointerType pointerType) {
PointerType pointer = pointerType;
if (pointer == null) { if (pointer == null) {
pointer = PointerType.FUTURE; pointer = PointerType.FUTURE;
} }
Span span; Span span;
if (pointer == PointerType.FUTURE) { if (pointer == PointerType.FUTURE) {
ZonedDateTime thisFortnightStart = ymdh(getNow()).plus(RepeaterHour.HOUR_SECONDS, ChronoUnit.SECONDS); ZonedDateTime thisFortnightStart = ymdh(getNow()).plus(RepeaterHour.HOUR_SECONDS, ChronoUnit.SECONDS);
@ -86,6 +86,18 @@ public class RepeaterFortnight extends RepeaterUnit {
return RepeaterFortnight.FORTNIGHT_SECONDS; return RepeaterFortnight.FORTNIGHT_SECONDS;
} }
@Override
public int hashCode() {
return super.hashCode() ^ getWidth();
}
@Override
public boolean equals(Object other) {
return other instanceof RepeaterFortnight &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
@Override @Override
public String toString() { public String toString() {
return super.toString() + "-fortnight"; return super.toString() + "-fortnight";

View file

@ -71,6 +71,18 @@ public class RepeaterHour extends RepeaterUnit {
return RepeaterHour.HOUR_SECONDS; return RepeaterHour.HOUR_SECONDS;
} }
@Override
public int hashCode() {
return super.hashCode() ^ getWidth();
}
@Override
public boolean equals(Object other) {
return other instanceof RepeaterHour &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
@Override @Override
public String toString() { public String toString() {
return super.toString() + "-hour"; return super.toString() + "-hour";

View file

@ -66,6 +66,18 @@ public class RepeaterMinute extends RepeaterUnit {
return RepeaterMinute.MINUTE_SECONDS; return RepeaterMinute.MINUTE_SECONDS;
} }
@Override
public int hashCode() {
return super.hashCode() ^ getWidth();
}
@Override
public boolean equals(Object other) {
return other instanceof RepeaterMinute &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
@Override @Override
public String toString() { public String toString() {
return super.toString() + "-minute"; return super.toString() + "-minute";

View file

@ -66,6 +66,18 @@ public class RepeaterMonth extends RepeaterUnit {
return RepeaterMonth.MONTH_SECONDS; return RepeaterMonth.MONTH_SECONDS;
} }
@Override
public int hashCode() {
return super.hashCode() ^ getWidth();
}
@Override
public boolean equals(Object other) {
return other instanceof RepeaterMonth &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
@Override @Override
public String toString() { public String toString() {
return super.toString() + "-month"; return super.toString() + "-month";

View file

@ -76,14 +76,14 @@ public class RepeaterMonthName extends Repeater<RepeaterMonthName.MonthName> {
} else if (pointer == PointerType.NONE) { } else if (pointer == PointerType.NONE) {
if (nowMonth <= targetMonth) { if (nowMonth <= targetMonth) {
currentMonthBegin = ZonedDateTime.of(getNow().getYear(), targetMonth, 1, 0, 0, 0, 0, getNow().getZone()); currentMonthBegin = ZonedDateTime.of(getNow().getYear(), targetMonth, 1, 0, 0, 0, 0, getNow().getZone());
} else if (nowMonth > targetMonth) { } else {
currentMonthBegin = ZonedDateTime.of(getNow().getYear(), targetMonth, 1, 0, 0, 0, 0, getNow().getZone()) currentMonthBegin = ZonedDateTime.of(getNow().getYear(), targetMonth, 1, 0, 0, 0, 0, getNow().getZone())
.plus(1, ChronoUnit.YEARS); .plus(1, ChronoUnit.YEARS);
} }
} else if (pointer == PointerType.PAST) { } else if (pointer == PointerType.PAST) {
if (nowMonth > targetMonth) { if (nowMonth > targetMonth) {
currentMonthBegin = ZonedDateTime.of(getNow().getYear(), targetMonth, 1, 0, 0, 0, 0, getNow().getZone()); currentMonthBegin = ZonedDateTime.of(getNow().getYear(), targetMonth, 1, 0, 0, 0, 0, getNow().getZone());
} else if (nowMonth <= targetMonth) { } else {
currentMonthBegin = ZonedDateTime.of(getNow().getYear(), targetMonth, 1, 0, 0, 0, 0, getNow().getZone()) currentMonthBegin = ZonedDateTime.of(getNow().getYear(), targetMonth, 1, 0, 0, 0, 0, getNow().getZone())
.minus(1, ChronoUnit.YEARS); .minus(1, ChronoUnit.YEARS);
} }
@ -129,6 +129,18 @@ public class RepeaterMonthName extends Repeater<RepeaterMonthName.MonthName> {
return RepeaterMonthName.MONTH_SECONDS; return RepeaterMonthName.MONTH_SECONDS;
} }
@Override
public int hashCode() {
return super.hashCode() ^ getWidth();
}
@Override
public boolean equals(Object other) {
return other instanceof RepeaterMonthName &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
@Override @Override
public String toString() { public String toString() {
return super.toString() + "-monthname-" + getType(); return super.toString() + "-monthname-" + getType();

View file

@ -10,7 +10,7 @@ import java.time.temporal.ChronoUnit;
* *
*/ */
public class RepeaterSecond extends RepeaterUnit { public class RepeaterSecond extends RepeaterUnit {
public static final int SECOND_SECONDS = 1; // (60 * 60); private static final int SECOND_SECONDS = 1;
private ZonedDateTime secondStart; private ZonedDateTime secondStart;
@ -42,6 +42,18 @@ public class RepeaterSecond extends RepeaterUnit {
return RepeaterSecond.SECOND_SECONDS; return RepeaterSecond.SECOND_SECONDS;
} }
@Override
public int hashCode() {
return super.hashCode() ^ getWidth();
}
@Override
public boolean equals(Object other) {
return other instanceof RepeaterSecond &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
@Override @Override
public String toString() { public String toString() {
return super.toString() + "-second"; return super.toString() + "-second";

View file

@ -37,8 +37,8 @@ public class RepeaterTime extends Repeater<Tick> {
int minutesInSeconds = Integer.parseInt(t.substring(1)) * 60; int minutesInSeconds = Integer.parseInt(t.substring(1)) * 60;
type = new Tick(hoursInSeconds + minutesInSeconds, true); type = new Tick(hoursInSeconds + minutesInSeconds, true);
} else if (length == 4) { } else if (length == 4) {
boolean ambiguous = (time.contains(":") && Integer.parseInt(t.substring(0, 1)) != 0 && boolean ambiguous = time.contains(":") && Integer.parseInt(t.substring(0, 1)) != 0 &&
Integer.parseInt(t.substring(0, 2)) <= 12); Integer.parseInt(t.substring(0, 2)) <= 12;
int hours = Integer.parseInt(t.substring(0, 2)); int hours = Integer.parseInt(t.substring(0, 2));
int hoursInSeconds = hours * 60 * 60; int hoursInSeconds = hours * 60 * 60;
int minutesInSeconds = Integer.parseInt(t.substring(2)) * 60; int minutesInSeconds = Integer.parseInt(t.substring(2)) * 60;
@ -53,8 +53,8 @@ public class RepeaterTime extends Repeater<Tick> {
int seconds = Integer.parseInt(t.substring(3)); int seconds = Integer.parseInt(t.substring(3));
type = new Tick(hoursInSeconds + minutesInSeconds + seconds, true); type = new Tick(hoursInSeconds + minutesInSeconds + seconds, true);
} else if (length == 6) { } else if (length == 6) {
boolean ambiguous = (time.contains(":") && Integer.parseInt(t.substring(0, 1)) != 0 && boolean ambiguous = time.contains(":") && Integer.parseInt(t.substring(0, 1)) != 0 &&
Integer.parseInt(t.substring(0, 2)) <= 12); Integer.parseInt(t.substring(0, 2)) <= 12;
int hours = Integer.parseInt(t.substring(0, 2)); int hours = Integer.parseInt(t.substring(0, 2));
int hoursInSeconds = hours * 60 * 60; int hoursInSeconds = hours * 60 * 60;
int minutesInSeconds = Integer.parseInt(t.substring(2, 4)) * 60; int minutesInSeconds = Integer.parseInt(t.substring(2, 4)) * 60;
@ -136,7 +136,7 @@ public class RepeaterTime extends Repeater<Tick> {
if (tick.isAmbiguous()) { if (tick.isAmbiguous()) {
List<ZonedDateTime> futureDates = new LinkedList<>(); List<ZonedDateTime> futureDates = new LinkedList<>();
futureDates.add(midnight.plus(tick.intValue(), ChronoUnit.SECONDS)); futureDates.add(midnight.plus(tick.intValue(), ChronoUnit.SECONDS));
futureDates.add(midnight.plus(halfDay + tick.intValue(), ChronoUnit.SECONDS)); futureDates.add(midnight.plus(halfDay + (long) tick.intValue(), ChronoUnit.SECONDS));
futureDates.add(tomorrowMidnight.plus(tick.intValue(), ChronoUnit.SECONDS)); futureDates.add(tomorrowMidnight.plus(tick.intValue(), ChronoUnit.SECONDS));
for (ZonedDateTime futureDate : futureDates) { for (ZonedDateTime futureDate : futureDates) {
if (futureDate.isAfter(now) || futureDate.equals(now)) { if (futureDate.isAfter(now) || futureDate.equals(now)) {
@ -160,9 +160,9 @@ public class RepeaterTime extends Repeater<Tick> {
} else { } else {
if (tick.isAmbiguous()) { if (tick.isAmbiguous()) {
List<ZonedDateTime> pastDates = new LinkedList<>(); List<ZonedDateTime> pastDates = new LinkedList<>();
pastDates.add(midnight.plus(halfDay + tick.intValue(), ChronoUnit.SECONDS)); pastDates.add(midnight.plus(halfDay + (long) tick.intValue(), ChronoUnit.SECONDS));
pastDates.add(midnight.plus(tick.intValue(), ChronoUnit.SECONDS)); pastDates.add(midnight.plus(tick.intValue(), ChronoUnit.SECONDS));
pastDates.add(yesterdayMidnight.plus(tick.intValue() * 2, ChronoUnit.SECONDS)); pastDates.add(yesterdayMidnight.plus(tick.intValue() * 2L, ChronoUnit.SECONDS));
for (ZonedDateTime pastDate : pastDates) { for (ZonedDateTime pastDate : pastDates) {
if (pastDate.isBefore(now) || pastDate.equals(now)) { if (pastDate.isBefore(now) || pastDate.equals(now)) {
currentTime = pastDate; currentTime = pastDate;
@ -190,8 +190,8 @@ public class RepeaterTime extends Repeater<Tick> {
} }
if (!first) { if (!first) {
int increment = (tick.isAmbiguous()) ? halfDay : fullDay; int increment = tick.isAmbiguous() ? halfDay : fullDay;
int direction = (pointer == PointerType.FUTURE) ? 1 : -1; long direction = pointer == PointerType.FUTURE ? 1L : -1L;
currentTime = currentTime.plus(direction * increment, ChronoUnit.SECONDS); currentTime = currentTime.plus(direction * increment, ChronoUnit.SECONDS);
} }
@ -200,10 +200,7 @@ public class RepeaterTime extends Repeater<Tick> {
@Override @Override
protected Span internalThisSpan(PointerType pointer) { protected Span internalThisSpan(PointerType pointer) {
if (pointer == PointerType.NONE) { return nextSpan(pointer == PointerType.NONE ? PointerType.FUTURE : pointer);
pointer = PointerType.FUTURE;
}
return nextSpan(pointer);
} }
@Override @Override
@ -216,6 +213,18 @@ public class RepeaterTime extends Repeater<Tick> {
return 1; return 1;
} }
@Override
public int hashCode() {
return super.hashCode() ^ getWidth();
}
@Override
public boolean equals(Object other) {
return other instanceof RepeaterTime &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
@Override @Override
public String toString() { public String toString() {
return super.toString() + "-time-" + getType(); return super.toString() + "-time-" + getType();

View file

@ -47,11 +47,23 @@ public abstract class RepeaterUnit extends Repeater<Object> {
} }
} }
return null; return null;
} catch (Throwable t) { } catch (Exception e) {
throw new RuntimeException("Failed to create RepeaterUnit.", t); throw new IllegalStateException("Failed to create RepeaterUnit", e);
} }
} }
@Override
public int hashCode() {
return super.hashCode() ^ getWidth();
}
@Override
public boolean equals(Object other) {
return other instanceof RepeaterUnit &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
/** /**
* *
*/ */

View file

@ -10,7 +10,7 @@ import java.time.temporal.ChronoUnit;
* *
*/ */
public class RepeaterWeek extends RepeaterUnit { public class RepeaterWeek extends RepeaterUnit {
public static final int WEEK_SECONDS = 604800; // (7 * 24 * 60 * 60); public static final int WEEK_SECONDS = 604800;
public static final int WEEK_DAYS = 7; public static final int WEEK_DAYS = 7;
private ZonedDateTime currentWeekStart; private ZonedDateTime currentWeekStart;
@ -38,7 +38,7 @@ public class RepeaterWeek extends RepeaterUnit {
throw new IllegalArgumentException("Unable to handle pointer " + pointer + "."); throw new IllegalArgumentException("Unable to handle pointer " + pointer + ".");
} }
} else { } else {
int direction = (pointer == PointerType.FUTURE) ? 1 : -1; long direction = pointer == PointerType.FUTURE ? 1L : -1L;
currentWeekStart = currentWeekStart.plus(RepeaterWeek.WEEK_DAYS * direction, ChronoUnit.DAYS); currentWeekStart = currentWeekStart.plus(RepeaterWeek.WEEK_DAYS * direction, ChronoUnit.DAYS);
} }
@ -88,6 +88,18 @@ public class RepeaterWeek extends RepeaterUnit {
return RepeaterWeek.WEEK_SECONDS; return RepeaterWeek.WEEK_SECONDS;
} }
@Override
public int hashCode() {
return super.hashCode() ^ getWidth();
}
@Override
public boolean equals(Object other) {
return other instanceof RepeaterWeek &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
@Override @Override
public String toString() { public String toString() {
return super.toString() + "-week"; return super.toString() + "-week";

View file

@ -16,25 +16,34 @@ public class RepeaterWeekend extends RepeaterUnit {
@Override @Override
protected Span internalNextSpan(PointerType pointer) { protected Span internalNextSpan(PointerType pointer) {
if (currentWeekStart == null) { if (currentWeekStart != null) {
if (pointer == PointerType.FUTURE) { long direction = pointer == PointerType.FUTURE ? 1L : -1L;
RepeaterDayName saturdayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SATURDAY); currentWeekStart = currentWeekStart.plus(direction * RepeaterWeek.WEEK_SECONDS, ChronoUnit.SECONDS);
saturdayRepeater.setNow(getNow()); ZonedDateTime c = currentWeekStart.plus(RepeaterWeekend.WEEKEND_SECONDS, ChronoUnit.SECONDS);
Span nextSaturdaySpan = saturdayRepeater.nextSpan(PointerType.FUTURE); return new Span(currentWeekStart, c);
currentWeekStart = nextSaturdaySpan.getBeginCalendar(); }
} else if (pointer == PointerType.PAST) { ZonedDateTime c;
switch (pointer) {
case PAST: {
RepeaterDayName saturdayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SATURDAY); RepeaterDayName saturdayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SATURDAY);
saturdayRepeater.setNow(getNow().plus(RepeaterDay.DAY_SECONDS, ChronoUnit.SECONDS)); saturdayRepeater.setNow(getNow().plus(RepeaterDay.DAY_SECONDS, ChronoUnit.SECONDS));
Span lastSaturdaySpan = saturdayRepeater.nextSpan(PointerType.PAST); Span lastSaturdaySpan = saturdayRepeater.nextSpan(PointerType.PAST);
currentWeekStart = lastSaturdaySpan.getBeginCalendar(); currentWeekStart = lastSaturdaySpan.getBeginCalendar();
c = currentWeekStart.plus(RepeaterWeekend.WEEKEND_SECONDS, ChronoUnit.SECONDS);
return new Span(currentWeekStart, c);
} }
} else { case FUTURE: {
long direction = pointer == PointerType.FUTURE ? 1L : -1L; RepeaterDayName saturdayRepeater = new RepeaterDayName(RepeaterDayName.DayName.SATURDAY);
currentWeekStart = currentWeekStart.plus(direction * RepeaterWeek.WEEK_SECONDS, ChronoUnit.SECONDS); saturdayRepeater.setNow(getNow());
Span nextSaturdaySpan = saturdayRepeater.nextSpan(PointerType.FUTURE);
currentWeekStart = nextSaturdaySpan.getBeginCalendar();
c = currentWeekStart.plus(RepeaterWeekend.WEEKEND_SECONDS, ChronoUnit.SECONDS);
return new Span(currentWeekStart, c);
}
default:
break;
} }
assert currentWeekStart != null; throw new IllegalArgumentException("pointer type not expected");
ZonedDateTime c = currentWeekStart.plus(RepeaterWeekend.WEEKEND_SECONDS, ChronoUnit.SECONDS);
return new Span(currentWeekStart, c);
} }
@Override @Override
@ -73,6 +82,18 @@ public class RepeaterWeekend extends RepeaterUnit {
return (int) RepeaterWeekend.WEEKEND_SECONDS; return (int) RepeaterWeekend.WEEKEND_SECONDS;
} }
@Override
public int hashCode() {
return super.hashCode() ^ getWidth();
}
@Override
public boolean equals(Object other) {
return other instanceof RepeaterWeekend &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
@Override @Override
public String toString() { public String toString() {
return super.toString() + "-weekend"; return super.toString() + "-weekend";

View file

@ -70,6 +70,18 @@ public class RepeaterYear extends RepeaterUnit {
return (365 * 24 * 60 * 60); return (365 * 24 * 60 * 60);
} }
@Override
public int hashCode() {
return super.hashCode() ^ getWidth();
}
@Override
public boolean equals(Object other) {
return other instanceof RepeaterYear &&
((Repeater) other).getType().equals(getType()) &&
((Repeater) other).getNow().equals(getNow());
}
@Override @Override
public String toString() { public String toString() {
return super.toString() + "-year"; return super.toString() + "-year";

View file

@ -0,0 +1,109 @@
package org.xbib.time.format;
import java.io.IOException;
import java.io.Writer;
import java.time.Period;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
/**
* Composite implementation that merges other fields to create a full pattern.
*/
class Composite implements PeriodPrinter, PeriodParser {
private final PeriodPrinter[] iPrinters;
private final PeriodParser[] iParsers;
Composite(List<Object> elementPairs) {
List<Object> printerList = new ArrayList<>();
List<Object> parserList = new ArrayList<>();
decompose(elementPairs, printerList, parserList);
if (printerList.isEmpty()) {
iPrinters = null;
} else {
iPrinters = printerList.toArray(new PeriodPrinter[printerList.size()]);
}
if (parserList.isEmpty()) {
iParsers = null;
} else {
iParsers = parserList.toArray(new PeriodParser[parserList.size()]);
}
}
public int countFieldsToPrint(Period period, int stopAt, Locale locale) {
int sum = 0;
PeriodPrinter[] printers = iPrinters;
for (int i = printers.length; sum < stopAt && --i >= 0; ) {
sum += printers[i].countFieldsToPrint(period, Integer.MAX_VALUE, locale);
}
return sum;
}
public int calculatePrintedLength(Period period, Locale locale) {
int sum = 0;
PeriodPrinter[] printers = iPrinters;
for (int i = printers.length; --i >= 0; ) {
sum += printers[i].calculatePrintedLength(period, locale);
}
return sum;
}
public void printTo(StringBuilder buf, Period period, Locale locale) {
for (PeriodPrinter printer : iPrinters) {
printer.printTo(buf, period, locale);
}
}
public void printTo(Writer out, Period period, Locale locale) throws IOException {
for (PeriodPrinter printer : iPrinters) {
printer.printTo(out, period, locale);
}
}
public int parseInto(PeriodAmount period, String periodStr, int pos, Locale locale) {
int position = pos;
PeriodParser[] parsers = iParsers;
if (parsers == null) {
throw new UnsupportedOperationException();
}
int len = parsers.length;
for (int i = 0; i < len && position >= 0; i++) {
position = parsers[i].parseInto(period, periodStr, position, locale);
}
return position;
}
private void decompose(List<Object> elementPairs, List<Object> printerList, List<Object> parserList) {
int size = elementPairs.size();
for (int i = 0; i < size; i += 2) {
Object element = elementPairs.get(i);
if (element instanceof PeriodPrinter) {
if (element instanceof Composite) {
addArrayToList(printerList, ((Composite) element).iPrinters);
} else {
printerList.add(element);
}
}
element = elementPairs.get(i + 1);
if (element instanceof PeriodParser) {
if (element instanceof Composite) {
addArrayToList(parserList, ((Composite) element).iParsers);
} else {
parserList.add(element);
}
}
}
}
private void addArrayToList(List<Object> list, Object[] array) {
if (array != null) {
Collections.addAll(list, array);
}
}
}

View file

@ -0,0 +1,82 @@
package org.xbib.time.format;
import java.io.IOException;
import java.io.Writer;
import java.util.HashSet;
import java.util.Set;
/**
* Builds a composite affix by merging two other affix implementations.
*/
class CompositeAffix extends IgnorableAffix {
private final PeriodFieldAffix iLeft;
private final PeriodFieldAffix iRight;
private final String[] iLeftRightCombinations;
CompositeAffix(PeriodFieldAffix left, PeriodFieldAffix right) {
iLeft = left;
iRight = right;
// We need to construct all possible combinations of left and right.
// We are doing it once in constructor so that getAffixes() is quicker.
Set<String> result = new HashSet<String>();
for (String leftText : iLeft.getAffixes()) {
for (String rightText : iRight.getAffixes()) {
result.add(leftText + rightText);
}
}
iLeftRightCombinations = result.toArray(new String[result.size()]);
}
@Override
public int calculatePrintedLength(int value) {
return iLeft.calculatePrintedLength(value)
+ iRight.calculatePrintedLength(value);
}
@Override
public void printTo(StringBuilder buf, int value) {
iLeft.printTo(buf, value);
iRight.printTo(buf, value);
}
@Override
public void printTo(Writer out, int value) throws IOException {
iLeft.printTo(out, value);
iRight.printTo(out, value);
}
@Override
public int parse(String periodStr, int position) {
int pos = iLeft.parse(periodStr, position);
if (pos >= 0) {
pos = iRight.parse(periodStr, pos);
if (pos >= 0 && matchesOtherAffix(parse(periodStr, pos) - pos, periodStr, position)) {
return ~position;
}
}
return pos;
}
@Override
public int scan(String periodStr, final int position) {
int leftPosition = iLeft.scan(periodStr, position);
if (leftPosition >= 0) {
int rightPosition = iRight.scan(periodStr, iLeft.parse(periodStr, leftPosition));
if (!(rightPosition >= 0 && matchesOtherAffix(iRight.parse(periodStr, rightPosition) -
leftPosition, periodStr, position))) {
if (leftPosition > 0) {
return leftPosition;
} else {
return rightPosition;
}
}
}
return ~position;
}
@Override
public String[] getAffixes() {
return iLeftRightCombinations.clone();
}
}

View file

@ -0,0 +1,303 @@
package org.xbib.time.format;
import java.io.IOException;
import java.io.Writer;
import java.time.Period;
import java.time.temporal.ChronoUnit;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
/**
* Formats the numeric value of a field, potentially with prefix/suffix.
*/
class FieldFormatter implements PeriodPrinter, PeriodParser {
private final int iMinPrintedDigits;
//private final int iPrintZeroSetting;
private final int iMaxParsedDigits;
private final boolean iRejectSignedValues;
/**
* The index of the field type, 0=year, etc.
*/
private final ChronoUnit unit;
/**
* The array of the latest formatter added for each type.
* This is shared between all the field formatters in a formatter.
*/
//private final FieldFormatter[] iFieldFormatters;
private final PeriodFieldAffix iPrefix;
private final PeriodFieldAffix iSuffix;
FieldFormatter(int minPrintedDigits,
int maxParsedDigits, boolean rejectSignedValues,
ChronoUnit chronoUnit,
PeriodFieldAffix prefix, PeriodFieldAffix suffix) {
iMinPrintedDigits = minPrintedDigits;
iMaxParsedDigits = maxParsedDigits;
iRejectSignedValues = rejectSignedValues;
this.unit = chronoUnit;
iPrefix = prefix;
iSuffix = suffix;
}
FieldFormatter(FieldFormatter field, PeriodFieldAffix periodFieldAffix) {
PeriodFieldAffix suffix = periodFieldAffix;
iMinPrintedDigits = field.iMinPrintedDigits;
iMaxParsedDigits = field.iMaxParsedDigits;
iRejectSignedValues = field.iRejectSignedValues;
this.unit = field.unit;
iPrefix = field.iPrefix;
if (field.iSuffix != null) {
suffix = new CompositeAffix(field.iSuffix, suffix);
}
iSuffix = suffix;
}
public void finish(FieldFormatter[] fieldFormatters) {
Set<PeriodFieldAffix> prefixesToIgnore = new HashSet<>();
Set<PeriodFieldAffix> suffixesToIgnore = new HashSet<>();
for (FieldFormatter fieldFormatter : fieldFormatters) {
if (fieldFormatter != null && !this.equals(fieldFormatter)) {
prefixesToIgnore.add(fieldFormatter.iPrefix);
suffixesToIgnore.add(fieldFormatter.iSuffix);
}
}
// if we have a prefix then allow ignore behaviour
if (iPrefix != null) {
iPrefix.finish(prefixesToIgnore);
}
// if we have a suffix then allow ignore behaviour
if (iSuffix != null) {
iSuffix.finish(suffixesToIgnore);
}
}
public int countFieldsToPrint(Period period, int stopAt, Locale locale) {
if (stopAt <= 0) {
return 0;
}
if (getFieldValue(period) != Long.MAX_VALUE) {
return 1;
}
return 0;
}
public int calculatePrintedLength(Period period, Locale locale) {
long valueLong = getFieldValue(period);
if (valueLong == Long.MAX_VALUE) {
return 0;
}
int sum = Math.max(FormatUtils.calculateDigitCount(valueLong), iMinPrintedDigits);
int value = (int) valueLong;
if (iPrefix != null) {
sum += iPrefix.calculatePrintedLength(value);
}
if (iSuffix != null) {
sum += iSuffix.calculatePrintedLength(value);
}
return sum;
}
public void printTo(StringBuilder buf, Period period, Locale locale) {
long valueLong = getFieldValue(period);
if (valueLong == Long.MAX_VALUE) {
return;
}
int value = (int) valueLong;
if (iPrefix != null) {
iPrefix.printTo(buf, value);
}
int minDigits = iMinPrintedDigits;
if (minDigits <= 1) {
FormatUtils.appendUnpaddedInteger(buf, value);
} else {
FormatUtils.appendPaddedInteger(buf, value, minDigits);
}
if (iSuffix != null) {
iSuffix.printTo(buf, value);
}
}
public void printTo(Writer out, Period period, Locale locale) throws IOException {
long valueLong = getFieldValue(period);
if (valueLong == Long.MAX_VALUE) {
return;
}
int value = (int) valueLong;
if (iPrefix != null) {
iPrefix.printTo(out, value);
}
int minDigits = iMinPrintedDigits;
if (minDigits <= 1) {
FormatUtils.writeUnpaddedInteger(out, value);
} else {
FormatUtils.writePaddedInteger(out, value, minDigits);
}
if (iSuffix != null) {
iSuffix.printTo(out, value);
}
}
public int parseInto(PeriodAmount period, String text, int pos, Locale locale) {
int position = pos;
if (position >= text.length()) {
return ~position;
}
if (iPrefix != null) {
position = iPrefix.parse(text, position);
if (position < 0) {
return position;
}
}
int limit;
limit = Math.min(iMaxParsedDigits, text.length() - position);
int length = 0;
boolean hasDigits = false;
boolean negative;
while (length < limit) {
char c = text.charAt(position + length);
if (length == 0 && (c == '-' || c == '+') && !iRejectSignedValues) {
negative = c == '-';
if (length + 1 >= limit ||
(c = text.charAt(position + length + 1)) < '0' || c > '9') {
break;
}
if (negative) {
length++;
} else {
position++;
}
limit = Math.min(limit + 1, text.length() - position);
continue;
}
if (c >= '0' && c <= '9') {
hasDigits = true;
} else {
break;
}
length++;
}
if (!hasDigits) {
return ~position;
}
setFieldValue(period, unit, parseInt(text, position, length));
position += length;
if (position >= 0 && iSuffix != null) {
position = iSuffix.parse(text, position);
}
return position;
}
/**
* @param text text to parse
* @param pos position in text
* @param len exact count of characters to parse
* @return parsed int value
*/
private int parseInt(String text, int pos, int len) {
int position = pos;
int length = len;
if (length >= 10) {
return Integer.parseInt(text.substring(position, position + length));
}
if (length <= 0) {
return 0;
}
int value = text.charAt(position++);
length--;
boolean negative;
if (value == '-') {
if (--length < 0) {
return 0;
}
negative = true;
value = text.charAt(position++);
} else {
negative = false;
}
value -= '0';
while (length-- > 0) {
value = ((value << 3) + (value << 1)) + text.charAt(position++) - '0';
}
return negative ? -value : value;
}
/**
* @return Long.MAX_VALUE if nothing to print, otherwise value
*/
long getFieldValue(Period period) {
long value;
switch (unit) {
default:
return Long.MAX_VALUE;
case YEARS:
value = period.get(ChronoUnit.YEARS);
break;
case MONTHS:
value = period.get(ChronoUnit.MONTHS);
break;
case WEEKS:
value = period.get(ChronoUnit.WEEKS);
break;
case DAYS:
value = period.get(ChronoUnit.DAYS);
break;
case HOURS:
value = period.get(ChronoUnit.HOURS);
break;
case MINUTES:
value = period.get(ChronoUnit.MINUTES);
break;
case SECONDS:
value = period.get(ChronoUnit.SECONDS);
break;
case MILLIS:
value = period.get(ChronoUnit.MILLIS);
break;
}
return value;
}
void setFieldValue(PeriodAmount period, ChronoUnit field, long value) {
switch (field) {
default:
break;
case YEARS:
period.set(ChronoUnit.YEARS, value);
break;
case MONTHS:
period.set(ChronoUnit.MONTHS, value);
break;
case WEEKS:
period.set(ChronoUnit.WEEKS, value);
break;
case DAYS:
period.set(ChronoUnit.DAYS, value);
break;
case HOURS:
period.set(ChronoUnit.HOURS, value);
break;
case MINUTES:
period.set(ChronoUnit.MINUTES, value);
break;
case SECONDS:
period.set(ChronoUnit.SECONDS, value);
break;
case MILLIS:
period.set(ChronoUnit.MILLIS, value);
break;
}
}
ChronoUnit getFieldType() {
return unit;
}
}

View file

@ -2,6 +2,8 @@ package org.xbib.time.format;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* Utility methods used by formatters. * Utility methods used by formatters.
@ -9,6 +11,8 @@ import java.io.Writer;
*/ */
public class FormatUtils { public class FormatUtils {
private static final Logger logger = Logger.getLogger(FormatUtils.class.getName());
private static final double LOG_10 = Math.log(10); private static final double LOG_10 = Math.log(10);
/** /**
@ -31,7 +35,7 @@ public class FormatUtils {
try { try {
appendPaddedInteger((Appendable) buf, value, size); appendPaddedInteger((Appendable) buf, value, size);
} catch (IOException e) { } catch (IOException e) {
// StringBuilder does not throw IOException logger.log(Level.FINE, e.getMessage(), e);
} }
} }
@ -42,11 +46,13 @@ public class FormatUtils {
* This method is optimized for converting small values to strings. * This method is optimized for converting small values to strings.
* *
* @param appenadble receives integer converted to a string * @param appenadble receives integer converted to a string
* @param value value to convert to a string * @param intvalue value to convert to a string
* @param size minimum amount of digits to append * @param digits minimum amount of digits to append
* @throws IOException exception * @throws IOException exception
*/ */
public static void appendPaddedInteger(Appendable appenadble, int value, int size) throws IOException { public static void appendPaddedInteger(Appendable appenadble, int intvalue, int digits) throws IOException {
int value = intvalue;
int size = digits;
if (value < 0) { if (value < 0) {
appenadble.append('-'); appenadble.append('-');
if (value != Integer.MIN_VALUE) { if (value != Integer.MIN_VALUE) {
@ -55,7 +61,8 @@ public class FormatUtils {
for (; size > 10; size--) { for (; size > 10; size--) {
appenadble.append('0'); appenadble.append('0');
} }
appenadble.append("" + -(long) Integer.MIN_VALUE); appenadble.append(Long.toString(Integer.MIN_VALUE));
//.append("" + -(long) Integer.MIN_VALUE)
return; return;
} }
} }
@ -76,15 +83,15 @@ public class FormatUtils {
// Append remainder by calculating (value - d * 10). // Append remainder by calculating (value - d * 10).
appenadble.append((char) (value - (d << 3) - (d << 1) + '0')); appenadble.append((char) (value - (d << 3) - (d << 1) + '0'));
} else { } else {
int digits; int d;
if (value < 1000) { if (value < 1000) {
digits = 3; d = 3;
} else if (value < 10000) { } else if (value < 10000) {
digits = 4; d = 4;
} else { } else {
digits = (int) (Math.log(value) / LOG_10) + 1; d = (int) (Math.log(value) / LOG_10) + 1;
} }
for (; size > digits; size--) { for (; size > d; size--) {
appenadble.append('0'); appenadble.append('0');
} }
appenadble.append(Integer.toString(value)); appenadble.append(Integer.toString(value));
@ -105,7 +112,7 @@ public class FormatUtils {
try { try {
appendPaddedInteger((Appendable) buf, value, size); appendPaddedInteger((Appendable) buf, value, size);
} catch (IOException e) { } catch (IOException e) {
// StringBuilder does not throw IOException logger.log(Level.FINE, e.getMessage(), e);
} }
} }
@ -116,12 +123,14 @@ public class FormatUtils {
* This method is optimized for converting small values to strings. * This method is optimized for converting small values to strings.
* *
* @param appendable receives integer converted to a string * @param appendable receives integer converted to a string
* @param value value to convert to a string * @param longvalue value to convert to a string
* @param size minimum amount of digits to append * @param digits minimum amount of digits to append
* @throws IOException exception * @throws IOException exception
*/ */
public static void appendPaddedInteger(Appendable appendable, long value, int size) throws IOException { public static void appendPaddedInteger(Appendable appendable, long longvalue, int digits) throws IOException {
long value = longvalue;
int intValue = (int) value; int intValue = (int) value;
int size = digits;
if (intValue == value) { if (intValue == value) {
appendPaddedInteger(appendable, intValue, size); appendPaddedInteger(appendable, intValue, size);
} else if (size <= 19) { } else if (size <= 19) {
@ -139,8 +148,8 @@ public class FormatUtils {
return; return;
} }
} }
int digits = (int) (Math.log(value) / LOG_10) + 1; int d = (int) (Math.log(value) / LOG_10) + 1;
for (; size > digits; size--) { for (; size > d; size--) {
appendable.append('0'); appendable.append('0');
} }
appendable.append(Long.toString(value)); appendable.append(Long.toString(value));
@ -154,12 +163,14 @@ public class FormatUtils {
* This method is optimized for converting small values to strings. * This method is optimized for converting small values to strings.
* *
* @param out receives integer converted to a string * @param out receives integer converted to a string
* @param value value to convert to a string * @param val value to convert to a string
* @param size minimum amount of digits to append * @param digits minimum amount of digits to append
* @throws IOException exception * @throws IOException exception
*/ */
public static void writePaddedInteger(Writer out, int value, int size) public static void writePaddedInteger(Writer out, int val, int digits)
throws IOException { throws IOException {
int value = val;
int size = digits;
if (value < 0) { if (value < 0) {
out.write('-'); out.write('-');
if (value != Integer.MIN_VALUE) { if (value != Integer.MIN_VALUE) {
@ -168,7 +179,8 @@ public class FormatUtils {
for (; size > 10; size--) { for (; size > 10; size--) {
out.write('0'); out.write('0');
} }
out.write("" + -(long) Integer.MIN_VALUE); //out.write("" + -(long) Integer.MIN_VALUE);
out.write(Long.toString(Integer.MIN_VALUE));
return; return;
} }
} }
@ -189,15 +201,15 @@ public class FormatUtils {
// Append remainder by calculating (value - d * 10). // Append remainder by calculating (value - d * 10).
out.write(value - (d << 3) - (d << 1) + '0'); out.write(value - (d << 3) - (d << 1) + '0');
} else { } else {
int digits; int d;
if (value < 1000) { if (value < 1000) {
digits = 3; d = 3;
} else if (value < 10000) { } else if (value < 10000) {
digits = 4; d = 4;
} else { } else {
digits = (int) (Math.log(value) / LOG_10) + 1; d = (int) (Math.log(value) / LOG_10) + 1;
} }
for (; size > digits; size--) { for (; size > d; size--) {
out.write('0'); out.write('0');
} }
out.write(Integer.toString(value)); out.write(Integer.toString(value));
@ -211,12 +223,13 @@ public class FormatUtils {
* This method is optimized for converting small values to strings. * This method is optimized for converting small values to strings.
* *
* @param out receives integer converted to a string * @param out receives integer converted to a string
* @param value value to convert to a string * @param longvalue value to convert to a string
* @param size minimum amount of digits to append * @param digits minimum amount of digits to append
* @throws IOException exception * @throws IOException exception
*/ */
public static void writePaddedInteger(Writer out, long value, int size) public static void writePaddedInteger(Writer out, long longvalue, int digits) throws IOException {
throws IOException { long value = longvalue;
int size = digits;
int intValue = (int) value; int intValue = (int) value;
if (intValue == value) { if (intValue == value) {
writePaddedInteger(out, intValue, size); writePaddedInteger(out, intValue, size);
@ -235,8 +248,8 @@ public class FormatUtils {
return; return;
} }
} }
int digits = (int) (Math.log(value) / LOG_10) + 1; int d = (int) (Math.log(value) / LOG_10) + 1;
for (; size > digits; size--) { for (; size > d; size--) {
out.write('0'); out.write('0');
} }
out.write(Long.toString(value)); out.write(Long.toString(value));
@ -254,7 +267,7 @@ public class FormatUtils {
try { try {
appendUnpaddedInteger((Appendable) buf, value); appendUnpaddedInteger((Appendable) buf, value);
} catch (IOException e) { } catch (IOException e) {
// StringBuilder do not throw IOException logger.log(Level.FINE, e.getMessage(), e);
} }
} }
@ -264,16 +277,17 @@ public class FormatUtils {
* This method is optimized for converting small values to strings. * This method is optimized for converting small values to strings.
* *
* @param appendable receives integer converted to a string * @param appendable receives integer converted to a string
* @param value value to convert to a string * @param val value to convert to a string
* @throws IOException exception * @throws IOException exception
*/ */
public static void appendUnpaddedInteger(Appendable appendable, int value) throws IOException { public static void appendUnpaddedInteger(Appendable appendable, int val) throws IOException {
int value = val;
if (value < 0) { if (value < 0) {
appendable.append('-'); appendable.append('-');
if (value != Integer.MIN_VALUE) { if (value != Integer.MIN_VALUE) {
value = -value; value = -value;
} else { } else {
appendable.append("" + -(long) Integer.MIN_VALUE); appendable.append(Long.toString(Integer.MIN_VALUE));
return; return;
} }
} }
@ -303,7 +317,7 @@ public class FormatUtils {
try { try {
appendUnpaddedInteger((Appendable) buf, value); appendUnpaddedInteger((Appendable) buf, value);
} catch (IOException e) { } catch (IOException e) {
// StringBuilder do not throw IOException logger.log(Level.FINE, e.getMessage(), e);
} }
} }
@ -331,17 +345,18 @@ public class FormatUtils {
* This method is optimized for converting small values to strings. * This method is optimized for converting small values to strings.
* *
* @param out receives integer converted to a string * @param out receives integer converted to a string
* @param value value to convert to a string * @param val value to convert to a string
* @throws IOException exception * @throws IOException exception
*/ */
public static void writeUnpaddedInteger(Writer out, int value) public static void writeUnpaddedInteger(Writer out, int val)
throws IOException { throws IOException {
int value = val;
if (value < 0) { if (value < 0) {
out.write('-'); out.write('-');
if (value != Integer.MIN_VALUE) { if (value != Integer.MIN_VALUE) {
value = -value; value = -value;
} else { } else {
out.write("" + -(long) Integer.MIN_VALUE); out.write(Long.toString(Integer.MIN_VALUE));
return; return;
} }
} }
@ -394,12 +409,8 @@ public class FormatUtils {
return 20; return 20;
} }
} }
return return value < 10 ? 1 : value < 100 ? 2 : value < 1000 ? 3 : value < 10000 ? 4 :
(value < 10 ? 1 : ((int) (Math.log(value) / LOG_10) + 1);
(value < 100 ? 2 :
(value < 1000 ? 3 :
(value < 10000 ? 4 :
((int) (Math.log(value) / LOG_10) + 1)))));
} }
static int parseTwoDigits(CharSequence text, int position) { static int parseTwoDigits(CharSequence text, int position) {

View file

@ -0,0 +1,69 @@
package org.xbib.time.format;
import java.util.HashSet;
import java.util.Set;
/**
* An affix that can be ignored.
*/
abstract class IgnorableAffix implements PeriodFieldAffix {
private volatile String[] iOtherAffixes;
public void finish(Set<PeriodFieldAffix> periodFieldAffixesToIgnore) {
if (iOtherAffixes == null) {
// Calculate the shortest affix in this instance.
int shortestAffixLength = Integer.MAX_VALUE;
String shortestAffix = null;
for (String affix : getAffixes()) {
if (affix.length() < shortestAffixLength) {
shortestAffixLength = affix.length();
shortestAffix = affix;
}
}
// Pick only affixes that are longer than the shortest affix in this instance.
// This will reduce the number of parse operations and thus speed up the PeriodFormatter.
// also need to pick affixes that differ only in case (but not those that are identical)
Set<String> affixesToIgnore = new HashSet<String>();
for (PeriodFieldAffix periodFieldAffixToIgnore : periodFieldAffixesToIgnore) {
if (periodFieldAffixToIgnore != null) {
for (String affixToIgnore : periodFieldAffixToIgnore.getAffixes()) {
if (affixToIgnore.length() > shortestAffixLength ||
(affixToIgnore.equalsIgnoreCase(shortestAffix) &&
!affixToIgnore.equals(shortestAffix))) {
affixesToIgnore.add(affixToIgnore);
}
}
}
}
iOtherAffixes = affixesToIgnore.toArray(new String[affixesToIgnore.size()]);
}
}
/**
* Checks if there is a match among the other affixes (stored internally)
* that is longer than the passed value (textLength).
*
* @param textLength the length of the match
* @param periodStr the Period string that will be parsed
* @param position the position in the Period string at which the parsing should be started.
* @return true if the other affixes (stored internally) contain a match
* that is longer than the textLength parameter, false otherwise
*/
protected boolean matchesOtherAffix(int textLength, String periodStr, int position) {
if (iOtherAffixes != null) {
// ignore case when affix length differs
// match case when affix length is same
for (String affixToIgnore : iOtherAffixes) {
int textToIgnoreLength = affixToIgnore.length();
if ((textLength < textToIgnoreLength &&
periodStr.regionMatches(true, position, affixToIgnore, 0, textToIgnoreLength)) ||
(textLength == textToIgnoreLength &&
periodStr.regionMatches(false, position, affixToIgnore, 0, textToIgnoreLength))) {
return true;
}
}
}
return false;
}
}

View file

@ -0,0 +1,42 @@
package org.xbib.time.format;
import java.io.IOException;
import java.io.Writer;
import java.time.Period;
import java.util.Locale;
/**
* Handles a simple literal piece of text.
*/
class Literal implements PeriodPrinter, PeriodParser {
static final Literal EMPTY = new Literal("");
private final String iText;
Literal(String text) {
iText = text;
}
public int countFieldsToPrint(Period period, int stopAt, Locale locale) {
return 0;
}
public int calculatePrintedLength(Period period, Locale locale) {
return iText.length();
}
public void printTo(StringBuilder buf, Period period, Locale locale) {
buf.append(iText);
}
public void printTo(Writer out, Period period, Locale locale) throws IOException {
out.write(iText);
}
public int parseInto(PeriodAmount period, String periodStr,
int position, Locale locale) {
if (periodStr.regionMatches(true, position, iText, 0, iText.length())) {
return position + iText.length();
}
return ~position;
}
}

View file

@ -0,0 +1,46 @@
package org.xbib.time.format;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;
/**
* Defines a formatted field's prefix or suffix text.
* This can be used for fields such as 'n hours' or 'nH' or 'Hour:n'.
*/
interface PeriodFieldAffix {
int calculatePrintedLength(int value);
void printTo(StringBuilder buf, int value);
void printTo(Writer out, int value) throws IOException;
/**
* @return new position after parsing affix, or ~position of failure
*/
int parse(String periodStr, int position);
/**
* @return position where affix starts, or original ~position if not found
*/
int scan(String periodStr, int position);
/**
* @return a copy of array of affixes
*/
String[] getAffixes();
/**
* This method should be called only once.
* After first call consecutive calls to this methods will have no effect.
* Causes this affix to ignore a match (parse and scan
* methods) if there is an affix in the passed list that holds
* affix text which satisfy both following conditions:
* - the affix text is also a match
* - the affix text is longer than the match from this object
*
* @param affixesToIgnore affixes to ignore
*/
void finish(Set<PeriodFieldAffix> affixesToIgnore);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,82 @@
package org.xbib.time.format;
import java.io.IOException;
import java.io.Writer;
/**
* Implements an affix where the text varies by the amount of the field.
* Only singular (1) and plural (not 1) are supported.
*/
class PluralAffix extends IgnorableAffix {
private final String iSingularText;
private final String iPluralText;
PluralAffix(String singularText, String pluralText) {
iSingularText = singularText;
iPluralText = pluralText;
}
public int calculatePrintedLength(int value) {
return (value == 1 ? iSingularText : iPluralText).length();
}
public void printTo(StringBuilder buf, int value) {
buf.append(value == 1 ? iSingularText : iPluralText);
}
public void printTo(Writer out, int value) throws IOException {
out.write(value == 1 ? iSingularText : iPluralText);
}
public int parse(String periodStr, int position) {
String text1 = iPluralText;
String text2 = iSingularText;
if (text1.length() < text2.length()) {
// Swap in order to match longer one first.
String temp = text1;
text1 = text2;
text2 = temp;
}
if (periodStr.regionMatches(true, position, text1, 0, text1.length()) &&
!matchesOtherAffix(text1.length(), periodStr, position)) {
return position + text1.length();
}
if (periodStr.regionMatches(true, position, text2, 0, text2.length()) &&
!matchesOtherAffix(text2.length(), periodStr, position)) {
return position + text2.length();
}
return ~position;
}
public int scan(String periodStr, final int position) {
String text1 = iPluralText;
String text2 = iSingularText;
if (text1.length() < text2.length()) {
// Swap in order to match longer one first.
String temp = text1;
text1 = text2;
text2 = temp;
}
int textLength1 = text1.length();
int textLength2 = text2.length();
int sourceLength = periodStr.length();
for (int pos = position; pos < sourceLength; pos++) {
if (periodStr.regionMatches(true, pos, text1, 0, textLength1) &&
!matchesOtherAffix(text1.length(), periodStr, pos)) {
return pos;
}
if (periodStr.regionMatches(true, pos, text2, 0, textLength2) &&
!matchesOtherAffix(text2.length(), periodStr, pos)) {
return pos;
}
}
return ~position;
}
public String[] getAffixes() {
return new String[]{iSingularText, iPluralText};
}
}

View file

@ -0,0 +1,97 @@
package org.xbib.time.format;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.Comparator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;
/**
* Implements an affix where the text varies by the amount of the field.
* Different amounts are supported based on the provided parameters.
*/
class RegExAffix extends IgnorableAffix {
private static final ConcurrentMap<String, Pattern> PATTERNS = new ConcurrentHashMap<>();
private static final Comparator<String> LENGTH_DESC_COMPARATOR = (o1, o2) -> o2.length() - o1.length();
private final String[] iSuffixes;
private final Pattern[] iPatterns;
// The parse method has to iterate over the suffixes from the longest one to the shortest one
// Otherwise it might consume not enough characters.
private final String[] iSuffixesSortedDescByLength;
RegExAffix(String[] regExes, String[] texts) {
iSuffixes = texts.clone();
iPatterns = new Pattern[regExes.length];
for (int i = 0; i < regExes.length; i++) {
Pattern pattern = PATTERNS.get(regExes[i]);
if (pattern == null) {
pattern = Pattern.compile(regExes[i]);
Pattern p = PATTERNS.putIfAbsent(regExes[i], pattern);
}
iPatterns[i] = pattern;
}
iSuffixesSortedDescByLength = iSuffixes.clone();
Arrays.sort(iSuffixesSortedDescByLength, LENGTH_DESC_COMPARATOR);
}
private int selectSuffixIndex(int value) {
String valueString = String.valueOf(value);
for (int i = 0; i < iPatterns.length; i++) {
if (iPatterns[i].matcher(valueString).matches()) {
return i;
}
}
return iPatterns.length - 1;
}
@Override
public int calculatePrintedLength(int value) {
return iSuffixes[selectSuffixIndex(value)].length();
}
@Override
public void printTo(StringBuilder buf, int value) {
buf.append(iSuffixes[selectSuffixIndex(value)]);
}
@Override
public void printTo(Writer out, int value) throws IOException {
out.write(iSuffixes[selectSuffixIndex(value)]);
}
@Override
public int parse(String periodStr, int position) {
for (String text : iSuffixesSortedDescByLength) {
if (periodStr.regionMatches(true, position, text, 0, text.length()) &&
!matchesOtherAffix(text.length(), periodStr, position)) {
return position + text.length();
}
}
return ~position;
}
@Override
public int scan(String periodStr, final int position) {
int sourceLength = periodStr.length();
for (int pos = position; pos < sourceLength; pos++) {
for (String text : iSuffixesSortedDescByLength) {
if (periodStr.regionMatches(true, pos, text, 0, text.length()) &&
!matchesOtherAffix(text.length(), periodStr, pos)) {
return pos;
}
}
}
return ~position;
}
@Override
public String[] getAffixes() {
return iSuffixes.clone();
}
}

View file

@ -0,0 +1,180 @@
package org.xbib.time.format;
import java.io.IOException;
import java.io.Writer;
import java.time.Period;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Locale;
import java.util.TreeSet;
/**
* Handles a separator, that splits the fields into multiple parts.
* For example, the 'T' in the ISO8601 standard.
*/
class Separator implements PeriodPrinter, PeriodParser {
private final String iText;
private final String iFinalText;
private final String[] iParsedForms;
private final boolean iUseBefore;
private final boolean iUseAfter;
private final PeriodPrinter iBeforePrinter;
private final PeriodParser iBeforeParser;
volatile PeriodPrinter iAfterPrinter;
volatile PeriodParser iAfterParser;
Separator(String text, String finalText, String[] variants,
PeriodPrinter beforePrinter, PeriodParser beforeParser,
boolean useBefore, boolean useAfter) {
iText = text;
iFinalText = finalText;
if ((finalText == null || text.equals(finalText)) &&
(variants == null || variants.length == 0)) {
iParsedForms = new String[]{text};
} else {
// Filter and reverse sort the parsed forms.
TreeSet<String> parsedSet = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
parsedSet.add(text);
parsedSet.add(finalText);
if (variants != null) {
for (int i = variants.length; --i >= 0; ) {
parsedSet.add(variants[i]);
}
}
ArrayList<String> parsedList = new ArrayList<String>(parsedSet);
Collections.reverse(parsedList);
iParsedForms = parsedList.toArray(new String[parsedList.size()]);
}
iBeforePrinter = beforePrinter;
iBeforeParser = beforeParser;
iUseBefore = useBefore;
iUseAfter = useAfter;
}
public int countFieldsToPrint(Period period, int stopAt, Locale locale) {
int sum = iBeforePrinter.countFieldsToPrint(period, stopAt, locale);
if (sum < stopAt) {
sum += iAfterPrinter.countFieldsToPrint(period, stopAt, locale);
}
return sum;
}
public int calculatePrintedLength(Period period, Locale locale) {
PeriodPrinter before = iBeforePrinter;
PeriodPrinter after = iAfterPrinter;
int sum = before.calculatePrintedLength(period, locale)
+ after.calculatePrintedLength(period, locale);
if (iUseBefore) {
if (before.countFieldsToPrint(period, 1, locale) > 0) {
if (iUseAfter) {
int afterCount = after.countFieldsToPrint(period, 2, locale);
if (afterCount > 0) {
sum += (afterCount > 1 ? iText : iFinalText).length();
}
} else {
sum += iText.length();
}
}
} else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) {
sum += iText.length();
}
return sum;
}
public void printTo(StringBuilder buf, Period period, Locale locale) {
PeriodPrinter before = iBeforePrinter;
PeriodPrinter after = iAfterPrinter;
before.printTo(buf, period, locale);
if (iUseBefore) {
if (before.countFieldsToPrint(period, 1, locale) > 0) {
if (iUseAfter) {
int afterCount = after.countFieldsToPrint(period, 2, locale);
if (afterCount > 0) {
buf.append(afterCount > 1 ? iText : iFinalText);
}
} else {
buf.append(iText);
}
}
} else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) {
buf.append(iText);
}
after.printTo(buf, period, locale);
}
public void printTo(Writer out, Period period, Locale locale) throws IOException {
PeriodPrinter before = iBeforePrinter;
PeriodPrinter after = iAfterPrinter;
before.printTo(out, period, locale);
if (iUseBefore) {
if (before.countFieldsToPrint(period, 1, locale) > 0) {
if (iUseAfter) {
int afterCount = after.countFieldsToPrint(period, 2, locale);
if (afterCount > 0) {
out.write(afterCount > 1 ? iText : iFinalText);
}
} else {
out.write(iText);
}
}
} else if (iUseAfter && after.countFieldsToPrint(period, 1, locale) > 0) {
out.write(iText);
}
after.printTo(out, period, locale);
}
public int parseInto(PeriodAmount period, String periodStr,
int pos, Locale locale) {
int position = pos;
int oldPos = position;
position = iBeforeParser.parseInto(period, periodStr, position, locale);
if (position < 0) {
return position;
}
boolean found = false;
int parsedFormLength = -1;
if (position > oldPos) {
for (String parsedForm : iParsedForms) {
if ((parsedForm == null || parsedForm.length() == 0) ||
periodStr.regionMatches(true, position, parsedForm, 0, parsedForm.length())) {
parsedFormLength = (parsedForm == null ? 0 : parsedForm.length());
position += parsedFormLength;
found = true;
break;
}
}
}
oldPos = position;
position = iAfterParser.parseInto(period, periodStr, position, locale);
if (position < 0) {
return position;
}
if (found && position == oldPos && parsedFormLength > 0) {
// Separator should not have been supplied.
return ~oldPos;
}
if (position > oldPos && !found && !iUseBefore) {
// Separator was required.
return ~oldPos;
}
return position;
}
Separator finish(PeriodPrinter afterPrinter, PeriodParser afterParser) {
iAfterPrinter = afterPrinter;
iAfterParser = afterParser;
return this;
}
}

View file

@ -0,0 +1,75 @@
package org.xbib.time.format;
import java.io.IOException;
import java.io.Writer;
/**
* Implements an affix where the text does not vary by the amount.
*/
class SimpleAffix extends IgnorableAffix {
private final String iText;
SimpleAffix(String text) {
iText = text;
}
public int calculatePrintedLength(int value) {
return iText.length();
}
public void printTo(StringBuilder buf, int value) {
buf.append(iText);
}
public void printTo(Writer out, int value) throws IOException {
out.write(iText);
}
public int parse(String periodStr, int position) {
String text = iText;
int textLength = text.length();
if (periodStr.regionMatches(true, position, text, 0, textLength) &&
!matchesOtherAffix(textLength, periodStr, position)) {
return position + textLength;
}
return ~position;
}
public int scan(String periodStr, final int position) {
String text = iText;
int textLength = text.length();
int sourceLength = periodStr.length();
search:
for (int pos = position; pos < sourceLength; pos++) {
if (periodStr.regionMatches(true, pos, text, 0, textLength) &&
!matchesOtherAffix(textLength, periodStr, pos)) {
return pos;
}
// Only allow number characters to be skipped in search of suffix.
switch (periodStr.charAt(pos)) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '.':
case ',':
case '+':
case '-':
break;
default:
break search;
}
}
return ~position;
}
public String[] getAffixes() {
return new String[]{iText};
}
}

View file

@ -112,10 +112,7 @@ public class PrettyTime {
* @return time unit quantity * @return time unit quantity
*/ */
public TimeUnitQuantity approximateDuration(LocalDateTime then) { public TimeUnitQuantity approximateDuration(LocalDateTime then) {
if (then == null) { long difference = ChronoUnit.MILLIS.between(localDateTime, then == null ? LocalDateTime.now() : then);
then = LocalDateTime.now();
}
long difference = ChronoUnit.MILLIS.between(localDateTime, then);
return calculateDuration(difference); return calculateDuration(difference);
} }
@ -163,11 +160,8 @@ public class PrettyTime {
* between compared dates. * between compared dates.
*/ */
public List<TimeUnitQuantity> calculatePreciseDuration(LocalDateTime then) { public List<TimeUnitQuantity> calculatePreciseDuration(LocalDateTime then) {
if (then == null) {
then = LocalDateTime.now();
}
List<TimeUnitQuantity> result = new ArrayList<>(); List<TimeUnitQuantity> result = new ArrayList<>();
long difference = ChronoUnit.MILLIS.between(localDateTime, then); long difference = ChronoUnit.MILLIS.between(localDateTime, then == null ? LocalDateTime.now() : then);
TimeUnitQuantity timeUnitQuantity = calculateDuration(difference); TimeUnitQuantity timeUnitQuantity = calculateDuration(difference);
result.add(timeUnitQuantity); result.add(timeUnitQuantity);
while (timeUnitQuantity.getDelta() != 0L) { while (timeUnitQuantity.getDelta() != 0L) {
@ -191,10 +185,7 @@ public class PrettyTime {
* @return A formatted string representing {@code then} * @return A formatted string representing {@code then}
*/ */
public String format(LocalDateTime then) { public String format(LocalDateTime then) {
if (then == null) { return format(approximateDuration(then == null ? LocalDateTime.now() : then));
then = LocalDateTime.now();
}
return format(approximateDuration(then));
} }
/** /**

View file

@ -103,7 +103,7 @@ public class Resources_fi extends ListResourceBundle implements TimeFormatProvid
{"MillenniumPastSuffix", "sitten"}, {"MillenniumPastSuffix", "sitten"},
{"MillenniumFutureSuffix", "päästä"}, {"MillenniumFutureSuffix", "päästä"},
}; };
private volatile ConcurrentMap<TimeUnit, TimeFormat> formatMap = new ConcurrentHashMap<TimeUnit, TimeFormat>(); private volatile ConcurrentMap<TimeUnit, TimeFormat> formatMap = new ConcurrentHashMap<>();
public Resources_fi() { public Resources_fi() {
} }
@ -229,7 +229,7 @@ public class Resources_fi extends ListResourceBundle implements TimeFormatProvid
@Override @Override
public String decorate(TimeUnitQuantity timeUnitQuantity, String time) { public String decorate(TimeUnitQuantity timeUnitQuantity, String time) {
String result = ""; String result;
if (timeUnitQuantity.getUnit() instanceof Day && Math.abs(timeUnitQuantity.getQuantity()) == 1) { if (timeUnitQuantity.getUnit() instanceof Day && Math.abs(timeUnitQuantity.getQuantity()) == 1) {
result = time; result = time;
} else { } else {

View file

@ -10,7 +10,7 @@ import java.time.ZonedDateTime;
public class ParserTest extends Assert { public class ParserTest extends Assert {
private final static ZoneId ZONE_ID = ZoneId.of("GMT"); private static final ZoneId ZONE_ID = ZoneId.of("GMT");
private ZonedDateTime time_2006_08_16_14_00_00 = construct(2006, 8, 16, 14, 0, 0); private ZonedDateTime time_2006_08_16_14_00_00 = construct(2006, 8, 16, 14, 0, 0);
public static ZonedDateTime construct(int year, int month) { public static ZonedDateTime construct(int year, int month) {