001 /*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2014 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube 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 * SonarQube 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 */
020
021 package org.sonar.api.utils;
022
023 import org.apache.commons.lang.StringUtils;
024 import org.apache.commons.lang.builder.ToStringBuilder;
025 import org.apache.commons.lang.builder.ToStringStyle;
026
027 import javax.annotation.Nullable;
028
029 import java.io.Serializable;
030 import java.util.regex.Matcher;
031 import java.util.regex.Pattern;
032
033 /**
034 * @since 4.3
035 */
036 public class Duration implements Serializable {
037
038 public static final String DAY = "d";
039 public static final String HOUR = "h";
040 public static final String MINUTE = "min";
041
042 private static final short MINUTES_IN_ONE_HOUR = 60;
043
044 private final long durationInMinutes;
045
046 private Duration(long durationInMinutes) {
047 this.durationInMinutes = durationInMinutes;
048 }
049
050 private Duration(int days, int hours, int minutes, int hoursInDay) {
051 this(((long) days * hoursInDay * MINUTES_IN_ONE_HOUR) + (hours * MINUTES_IN_ONE_HOUR) + minutes);
052 }
053
054 /**
055 * Create a Duration from a number of minutes.
056 */
057 public static Duration create(long durationInMinutes) {
058 return new Duration(durationInMinutes);
059 }
060
061 /**
062 * Create a Duration from a text duration and the number of hours in a day.
063 * <br>
064 * For instance, Duration.decode("1d 1h", 8) will have a number of minutes of 540 (1*8*60 + 60).
065 * */
066 public static Duration decode(String text, int hoursInDay) {
067 int days = 0, hours = 0, minutes = 0;
068 String sanitizedText = StringUtils.deleteWhitespace(text);
069 Pattern pattern = Pattern.compile("\\s*+(?:(\\d++)\\s*+" + DAY + ")?+\\s*+(?:(\\d++)\\s*+" + HOUR + ")?+\\s*+(?:(\\d++)\\s*+" + MINUTE + ")?+\\s*+");
070 Matcher matcher = pattern.matcher(text);
071
072 try {
073 if (matcher.find()) {
074 String daysDuration = matcher.group(1);
075 if (daysDuration != null) {
076 days = Integer.parseInt(daysDuration);
077 sanitizedText = sanitizedText.replace(daysDuration + DAY, "");
078 }
079 String hoursText = matcher.group(2);
080 if (hoursText != null) {
081 hours = Integer.parseInt(hoursText);
082 sanitizedText = sanitizedText.replace(hoursText + HOUR, "");
083 }
084 String minutesText = matcher.group(3);
085 if (minutesText != null) {
086 minutes = Integer.parseInt(minutesText);
087 sanitizedText = sanitizedText.replace(minutesText + MINUTE, "");
088 }
089 if (sanitizedText.isEmpty()) {
090 return new Duration(days, hours, minutes, hoursInDay);
091 }
092 }
093 throw invalid(text, null);
094 } catch (NumberFormatException e) {
095 throw invalid(text, e);
096 }
097 }
098
099 /**
100 * Return the duration in text, by using the given hours in day parameter to process hours.
101 * <br/>
102 * Duration.decode("1d 1h", 8).encode(8) will return "1d 1h"
103 * Duration.decode("2d", 8).encode(16) will return "1d"
104 */
105 public String encode(int hoursInDay) {
106 int days = ((Double) ((double) durationInMinutes / hoursInDay / MINUTES_IN_ONE_HOUR)).intValue();
107 Long remainingDuration = durationInMinutes - (days * hoursInDay * MINUTES_IN_ONE_HOUR);
108 int hours = ((Double) (remainingDuration.doubleValue() / MINUTES_IN_ONE_HOUR)).intValue();
109 remainingDuration = remainingDuration - (hours * MINUTES_IN_ONE_HOUR);
110 int minutes = remainingDuration.intValue();
111
112 StringBuilder stringBuilder = new StringBuilder();
113 if (days > 0) {
114 stringBuilder.append(days);
115 stringBuilder.append(DAY);
116 }
117 if (hours > 0) {
118 stringBuilder.append(hours);
119 stringBuilder.append(HOUR);
120 }
121 if (minutes > 0) {
122 stringBuilder.append(minutes);
123 stringBuilder.append(MINUTE);
124 }
125 return stringBuilder.toString();
126 }
127
128 /**
129 * Return the duration in minutes.
130 * <br>
131 * For instance, Duration.decode(1h, 24).toMinutes() will return 60.
132 */
133 public long toMinutes() {
134 return durationInMinutes;
135 }
136
137 /**
138 * Return true if the given duration is greater than the current one.
139 */
140 public boolean isGreaterThan(Duration other) {
141 return toMinutes() > other.toMinutes();
142 }
143
144 /**
145 * Add the given duration to the current one.
146 */
147 public Duration add(Duration with) {
148 return Duration.create(durationInMinutes + with.durationInMinutes);
149 }
150
151 /**
152 * Subtract the given duration to the current one.
153 */
154 public Duration subtract(Duration with) {
155 return Duration.create(durationInMinutes - with.durationInMinutes);
156 }
157
158 /**
159 * Multiply the duration with the given factor.
160 */
161 public Duration multiply(int factor) {
162 return Duration.create(durationInMinutes * factor);
163 }
164
165 private static IllegalArgumentException invalid(String text, @Nullable Exception e) {
166 throw new IllegalArgumentException(String.format("Duration '%s' is invalid, it should use the following sample format : 2d 10h 15min", text), e);
167 }
168
169 @Override
170 public boolean equals(Object o) {
171 if (this == o) {
172 return true;
173 }
174 if (o == null || getClass() != o.getClass()) {
175 return false;
176 }
177
178 Duration that = (Duration) o;
179 if (durationInMinutes != that.durationInMinutes) {
180 return false;
181 }
182
183 return true;
184 }
185
186 @Override
187 public int hashCode() {
188 return (int) (durationInMinutes ^ (durationInMinutes >>> 32));
189 }
190
191 @Override
192 public String toString() {
193 return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
194 }
195 }