001/* 002 * SonarQube 003 * Copyright (C) 2009-2016 SonarSource SA 004 * mailto:contact AT sonarsource DOT com 005 * 006 * This program is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 */ 020package org.sonar.api.utils; 021 022import org.apache.commons.lang.StringUtils; 023import org.apache.commons.lang.builder.ToStringBuilder; 024import org.apache.commons.lang.builder.ToStringStyle; 025 026import javax.annotation.Nullable; 027 028import java.io.Serializable; 029import java.util.regex.Matcher; 030import java.util.regex.Pattern; 031 032/** 033 * @since 4.3 034 */ 035public class Duration implements Serializable { 036 037 public static final String DAY = "d"; 038 public static final String HOUR = "h"; 039 public static final String MINUTE = "min"; 040 041 private static final short MINUTES_IN_ONE_HOUR = 60; 042 043 private final long durationInMinutes; 044 045 private Duration(long durationInMinutes) { 046 this.durationInMinutes = durationInMinutes; 047 } 048 049 private Duration(int days, int hours, int minutes, int hoursInDay) { 050 this((days * hoursInDay * MINUTES_IN_ONE_HOUR) + (hours * MINUTES_IN_ONE_HOUR) + minutes); 051 } 052 053 /** 054 * Create a Duration from a number of minutes. 055 */ 056 public static Duration create(long durationInMinutes) { 057 return new Duration(durationInMinutes); 058 } 059 060 /** 061 * Create a Duration from a text duration and the number of hours in a day. 062 * <br> 063 * For instance, Duration.decode("1d 1h", 8) will have a number of minutes of 540 (1*8*60 + 60). 064 * */ 065 public static Duration decode(String text, int hoursInDay) { 066 int days = 0; 067 int hours = 0; 068 int minutes = 0; 069 String sanitizedText = StringUtils.deleteWhitespace(text); 070 Pattern pattern = Pattern.compile("\\s*+(?:(\\d++)\\s*+" + DAY + ")?+\\s*+(?:(\\d++)\\s*+" + HOUR + ")?+\\s*+(?:(\\d++)\\s*+" + MINUTE + ")?+\\s*+"); 071 Matcher matcher = pattern.matcher(text); 072 073 try { 074 if (matcher.find()) { 075 String daysDuration = matcher.group(1); 076 if (daysDuration != null) { 077 days = Integer.parseInt(daysDuration); 078 sanitizedText = sanitizedText.replace(daysDuration + DAY, ""); 079 } 080 String hoursText = matcher.group(2); 081 if (hoursText != null) { 082 hours = Integer.parseInt(hoursText); 083 sanitizedText = sanitizedText.replace(hoursText + HOUR, ""); 084 } 085 String minutesText = matcher.group(3); 086 if (minutesText != null) { 087 minutes = Integer.parseInt(minutesText); 088 sanitizedText = sanitizedText.replace(minutesText + MINUTE, ""); 089 } 090 if (sanitizedText.isEmpty()) { 091 return new Duration(days, hours, minutes, hoursInDay); 092 } 093 } 094 throw invalid(text, null); 095 } catch (NumberFormatException e) { 096 throw invalid(text, e); 097 } 098 } 099 100 /** 101 * Return the duration in text, by using the given hours in day parameter to process hours. 102 * <br/> 103 * Duration.decode("1d 1h", 8).encode(8) will return "1d 1h" 104 * Duration.decode("2d", 8).encode(16) will return "1d" 105 */ 106 public String encode(int hoursInDay) { 107 int days = ((Double) ((double) durationInMinutes / hoursInDay / MINUTES_IN_ONE_HOUR)).intValue(); 108 Long remainingDuration = durationInMinutes - (days * hoursInDay * MINUTES_IN_ONE_HOUR); 109 int hours = ((Double) (remainingDuration.doubleValue() / MINUTES_IN_ONE_HOUR)).intValue(); 110 remainingDuration = remainingDuration - (hours * MINUTES_IN_ONE_HOUR); 111 int minutes = remainingDuration.intValue(); 112 113 StringBuilder stringBuilder = new StringBuilder(); 114 if (days > 0) { 115 stringBuilder.append(days); 116 stringBuilder.append(DAY); 117 } 118 if (hours > 0) { 119 stringBuilder.append(hours); 120 stringBuilder.append(HOUR); 121 } 122 if (minutes > 0) { 123 stringBuilder.append(minutes); 124 stringBuilder.append(MINUTE); 125 } 126 return stringBuilder.toString(); 127 } 128 129 /** 130 * Return the duration in minutes. 131 * <br> 132 * For instance, Duration.decode(1h, 24).toMinutes() will return 60. 133 */ 134 public long toMinutes() { 135 return durationInMinutes; 136 } 137 138 /** 139 * Return true if the given duration is greater than the current one. 140 */ 141 public boolean isGreaterThan(Duration other) { 142 return toMinutes() > other.toMinutes(); 143 } 144 145 /** 146 * Add the given duration to the current one. 147 */ 148 public Duration add(Duration with) { 149 return Duration.create(durationInMinutes + with.durationInMinutes); 150 } 151 152 /** 153 * Subtract the given duration to the current one. 154 */ 155 public Duration subtract(Duration with) { 156 return Duration.create(durationInMinutes - with.durationInMinutes); 157 } 158 159 /** 160 * Multiply the duration with the given factor. 161 */ 162 public Duration multiply(int factor) { 163 return Duration.create(durationInMinutes * factor); 164 } 165 166 private static IllegalArgumentException invalid(String text, @Nullable Exception e) { 167 throw new IllegalArgumentException(String.format("Duration '%s' is invalid, it should use the following sample format : 2d 10h 15min", text), e); 168 } 169 170 @Override 171 public boolean equals(Object o) { 172 if (this == o) { 173 return true; 174 } 175 if (o == null || getClass() != o.getClass()) { 176 return false; 177 } 178 179 Duration that = (Duration) o; 180 return durationInMinutes == that.durationInMinutes; 181 182 } 183 184 @Override 185 public int hashCode() { 186 return (int) (durationInMinutes ^ (durationInMinutes >>> 32)); 187 } 188 189 @Override 190 public String toString() { 191 return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); 192 } 193}