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
021package org.sonar.api.utils;
022
023import org.apache.commons.lang.StringUtils;
024import org.apache.commons.lang.builder.ToStringBuilder;
025import org.apache.commons.lang.builder.ToStringStyle;
026
027import javax.annotation.Nullable;
028
029import java.io.Serializable;
030import java.util.regex.Matcher;
031import java.util.regex.Pattern;
032
033/**
034 * @since 4.3
035 */
036public 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((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}