package co.codewizards.cloudstore.core; import static co.codewizards.cloudstore.core.util.StringUtil.*; import java.text.ParseException; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; public class TimePeriod { private final long millis; private static final String delim = " \t\u202F\u2009\r\n"; private static final Set<Character> delimChars = new HashSet<Character>(delim.length()); static { for (char c : delim.toCharArray()) delimChars.add(c); } public TimePeriod(String string) throws ParseException { final Map<TimeUnit, Long> timeUnitMap = new HashMap<>(); final StringTokenizer st = new StringTokenizer(string, delim, true); int offset = 0; long value = Long.MIN_VALUE; String timeUnitString = null; while (st.hasMoreTokens()) { final String tok = st.nextToken(); if (! isDelim(tok)) { if (value == Long.MIN_VALUE) { // tok might be a combination of a number and a unit, if no delimiter is placed inbetween => try to split! final String[] numberAndTimeUnit = splitNumberAndTimeUnit(tok); try { value = Long.parseLong(numberAndTimeUnit[0]); } catch (NumberFormatException x) { throw new ParseException( String.format("The text '%s' at position %d (0-based) of the input '%s' is not a valid integer!", isEmpty(numberAndTimeUnit[0]) ? tok : numberAndTimeUnit[0], offset, string), offset); } timeUnitString = numberAndTimeUnit[1]; } else timeUnitString = tok; if (! isEmpty(timeUnitString)) { final TimeUnit timeUnit; try { timeUnit = TimeUnit.valueOf(timeUnitString); } catch (Exception x) { throw new ParseException( String.format("The text '%s' at position %d (0-based) of the input '%s' is not a valid time unit!", timeUnitString, offset, string), offset); } timeUnitMap.put(timeUnit, value); value = Long.MIN_VALUE; timeUnitString = null; } } offset += tok.length(); } if (value != Long.MIN_VALUE) throw new ParseException(String.format("The input '%s' is missing a time unit at its end!", string), offset); long millis = 0; for (Map.Entry<TimeUnit, Long> me : timeUnitMap.entrySet()) millis += me.getKey().toMillis(me.getValue()); this.millis = millis; } private static boolean isDelim(final String token) { if (token == null || token.isEmpty()) return true; for (final char c : token.toCharArray()) { if (! delimChars.contains(c)) return false; } return true; } private static String[] splitNumberAndTimeUnit(final String token) { if (token == null || token.isEmpty()) return new String[] { token, null }; int index = 0; while (Character.isDigit(token.charAt(index))) { if (++index >= token.length()) return new String[] { token, null }; } return new String[] { token.substring(0, index), token.substring(index) }; } public TimePeriod(long millis) { this.millis = millis; } @Override public String toString() { return toString(TimeUnit.getUniqueTimeUnitsOrderedByLengthDesc()); } public String toString(final TimeUnit ... timeUnits) { return toString(timeUnits == null ? null : new HashSet<>(Arrays.asList(timeUnits))); } public String toString(final Collection<TimeUnit> timeUnits) { final StringBuilder sb = new StringBuilder(); final Map<TimeUnit, Long> timeUnitMap = toTimeUnitMap(timeUnits); for (Map.Entry<TimeUnit, Long> me : timeUnitMap.entrySet()) { if (sb.length() > 0) sb.append(' '); sb.append(me.getValue()).append('\u202F').append(me.getKey()); // thin-space-separated } return sb.toString(); } /** * Gets the value of this time period in milliseconds. * @return the value of this time period in milliseconds. */ public long toMillis() { return millis; } public Map<TimeUnit, Long> toTimeUnitMap() { return toTimeUnitMap((Collection<TimeUnit>) null); } public Map<TimeUnit, Long> toTimeUnitMap(final TimeUnit ... timeUnits) { return toTimeUnitMap(timeUnits == null ? null : new HashSet<>(Arrays.asList(timeUnits))); } public Map<TimeUnit, Long> toTimeUnitMap(Collection<TimeUnit> timeUnits) { if (timeUnits == null) timeUnits = TimeUnit.getUniqueTimeUnitsOrderedByLengthAsc(); final Map<TimeUnit, Long> result = new LinkedHashMap<>(); long remaining = millis; for (final TimeUnit timeUnit : timeUnits) { final long v = remaining / timeUnit.toMillis(); remaining -= v * timeUnit.toMillis(); if (v != 0) result.put(timeUnit, v); } if (remaining != 0) result.put(TimeUnit.ms, remaining); return result; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (millis ^ (millis >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final TimePeriod other = (TimePeriod) obj; return this.millis == other.millis; } }