001package co.codewizards.cloudstore.core; 002 003import static co.codewizards.cloudstore.core.util.StringUtil.*; 004 005import java.text.ParseException; 006import java.util.Arrays; 007import java.util.Collection; 008import java.util.HashMap; 009import java.util.HashSet; 010import java.util.LinkedHashMap; 011import java.util.Map; 012import java.util.Set; 013import java.util.StringTokenizer; 014 015public class TimePeriod { 016 private final long millis; 017 018 private static final String delim = " \t\u202F\u2009\r\n"; 019 private static final Set<Character> delimChars = new HashSet<Character>(delim.length()); 020 static { 021 for (char c : delim.toCharArray()) 022 delimChars.add(c); 023 } 024 025 public TimePeriod(String string) throws ParseException { 026 final Map<TimeUnit, Long> timeUnitMap = new HashMap<>(); 027 final StringTokenizer st = new StringTokenizer(string, delim, true); 028 int offset = 0; 029 long value = Long.MIN_VALUE; 030 String timeUnitString = null; 031 while (st.hasMoreTokens()) { 032 final String tok = st.nextToken(); 033 if (! isDelim(tok)) { 034 if (value == Long.MIN_VALUE) { 035 // tok might be a combination of a number and a unit, if no delimiter is placed inbetween => try to split! 036 final String[] numberAndTimeUnit = splitNumberAndTimeUnit(tok); 037 try { 038 value = Long.parseLong(numberAndTimeUnit[0]); 039 } catch (NumberFormatException x) { 040 throw new ParseException( 041 String.format("The text '%s' at position %d (0-based) of the input '%s' is not a valid integer!", 042 isEmpty(numberAndTimeUnit[0]) ? tok : numberAndTimeUnit[0], offset, string), 043 offset); 044 } 045 046 timeUnitString = numberAndTimeUnit[1]; 047 } 048 else 049 timeUnitString = tok; 050 051 if (! isEmpty(timeUnitString)) { 052 final TimeUnit timeUnit; 053 try { 054 timeUnit = TimeUnit.valueOf(timeUnitString); 055 } catch (Exception x) { 056 throw new ParseException( 057 String.format("The text '%s' at position %d (0-based) of the input '%s' is not a valid time unit!", 058 timeUnitString, offset, string), 059 offset); 060 } 061 timeUnitMap.put(timeUnit, value); 062 value = Long.MIN_VALUE; 063 timeUnitString = null; 064 } 065 } 066 offset += tok.length(); 067 } 068 069 if (value != Long.MIN_VALUE) 070 throw new ParseException(String.format("The input '%s' is missing a time unit at its end!", string), offset); 071 072 long millis = 0; 073 for (Map.Entry<TimeUnit, Long> me : timeUnitMap.entrySet()) 074 millis += me.getKey().toMillis(me.getValue()); 075 076 this.millis = millis; 077 } 078 079 private static boolean isDelim(final String token) { 080 if (token == null || token.isEmpty()) 081 return true; 082 083 for (final char c : token.toCharArray()) { 084 if (! delimChars.contains(c)) 085 return false; 086 } 087 return true; 088 } 089 090 private static String[] splitNumberAndTimeUnit(final String token) { 091 if (token == null || token.isEmpty()) 092 return new String[] { token, null }; 093 094 int index = 0; 095 while (Character.isDigit(token.charAt(index))) { 096 if (++index >= token.length()) 097 return new String[] { token, null }; 098 } 099 return new String[] { token.substring(0, index), token.substring(index) }; 100 } 101 102 public TimePeriod(long millis) { 103 this.millis = millis; 104 } 105 106 @Override 107 public String toString() { 108 return toString(TimeUnit.getUniqueTimeUnitsOrderedByLengthDesc()); 109 } 110 111 public String toString(final TimeUnit ... timeUnits) { 112 return toString(timeUnits == null ? null : new HashSet<>(Arrays.asList(timeUnits))); 113 } 114 115 public String toString(final Collection<TimeUnit> timeUnits) { 116 final StringBuilder sb = new StringBuilder(); 117 final Map<TimeUnit, Long> timeUnitMap = toTimeUnitMap(timeUnits); 118 for (Map.Entry<TimeUnit, Long> me : timeUnitMap.entrySet()) { 119 if (sb.length() > 0) 120 sb.append(' '); 121 122 sb.append(me.getValue()).append('\u202F').append(me.getKey()); // thin-space-separated 123 } 124 return sb.toString(); 125 } 126 127 /** 128 * Gets the value of this time period in milliseconds. 129 * @return the value of this time period in milliseconds. 130 */ 131 public long toMillis() { 132 return millis; 133 } 134 135 public Map<TimeUnit, Long> toTimeUnitMap() { 136 return toTimeUnitMap((Collection<TimeUnit>) null); 137 } 138 139 public Map<TimeUnit, Long> toTimeUnitMap(final TimeUnit ... timeUnits) { 140 return toTimeUnitMap(timeUnits == null ? null : new HashSet<>(Arrays.asList(timeUnits))); 141 } 142 143 public Map<TimeUnit, Long> toTimeUnitMap(Collection<TimeUnit> timeUnits) { 144 if (timeUnits == null) 145 timeUnits = TimeUnit.getUniqueTimeUnitsOrderedByLengthAsc(); 146 147 final Map<TimeUnit, Long> result = new LinkedHashMap<>(); 148 long remaining = millis; 149 for (final TimeUnit timeUnit : timeUnits) { 150 final long v = remaining / timeUnit.toMillis(); 151 remaining -= v * timeUnit.toMillis(); 152 153 if (v != 0) 154 result.put(timeUnit, v); 155 } 156 157 if (remaining != 0) 158 result.put(TimeUnit.ms, remaining); 159 160 return result; 161 } 162 163 @Override 164 public int hashCode() { 165 final int prime = 31; 166 int result = 1; 167 result = prime * result + (int) (millis ^ (millis >>> 32)); 168 return result; 169 } 170 171 @Override 172 public boolean equals(Object obj) { 173 if (this == obj) return true; 174 if (obj == null) return false; 175 if (getClass() != obj.getClass()) return false; 176 177 final TimePeriod other = (TimePeriod) obj; 178 return this.millis == other.millis; 179 } 180 181}