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;
068    int hours = 0;
069    int minutes = 0;
070    String sanitizedText = StringUtils.deleteWhitespace(text);
071    Pattern pattern = Pattern.compile("\\s*+(?:(\\d++)\\s*+" + DAY + ")?+\\s*+(?:(\\d++)\\s*+" + HOUR + ")?+\\s*+(?:(\\d++)\\s*+" + MINUTE + ")?+\\s*+");
072    Matcher matcher = pattern.matcher(text);
073
074    try {
075      if (matcher.find()) {
076        String daysDuration = matcher.group(1);
077        if (daysDuration != null) {
078          days = Integer.parseInt(daysDuration);
079          sanitizedText = sanitizedText.replace(daysDuration + DAY, "");
080        }
081        String hoursText = matcher.group(2);
082        if (hoursText != null) {
083          hours = Integer.parseInt(hoursText);
084          sanitizedText = sanitizedText.replace(hoursText + HOUR, "");
085        }
086        String minutesText = matcher.group(3);
087        if (minutesText != null) {
088          minutes = Integer.parseInt(minutesText);
089          sanitizedText = sanitizedText.replace(minutesText + MINUTE, "");
090        }
091        if (sanitizedText.isEmpty()) {
092          return new Duration(days, hours, minutes, hoursInDay);
093        }
094      }
095      throw invalid(text, null);
096    } catch (NumberFormatException e) {
097      throw invalid(text, e);
098    }
099  }
100
101  /**
102   * Return the duration in text, by using the given hours in day parameter to process hours.
103   * <br/>
104   * Duration.decode("1d 1h", 8).encode(8) will return "1d 1h"
105   * Duration.decode("2d", 8).encode(16) will return "1d"
106   */
107  public String encode(int hoursInDay) {
108    int days = ((Double) ((double) durationInMinutes / hoursInDay / MINUTES_IN_ONE_HOUR)).intValue();
109    Long remainingDuration = durationInMinutes - (days * hoursInDay * MINUTES_IN_ONE_HOUR);
110    int hours = ((Double) (remainingDuration.doubleValue() / MINUTES_IN_ONE_HOUR)).intValue();
111    remainingDuration = remainingDuration - (hours * MINUTES_IN_ONE_HOUR);
112    int minutes = remainingDuration.intValue();
113
114    StringBuilder stringBuilder = new StringBuilder();
115    if (days > 0) {
116      stringBuilder.append(days);
117      stringBuilder.append(DAY);
118    }
119    if (hours > 0) {
120      stringBuilder.append(hours);
121      stringBuilder.append(HOUR);
122    }
123    if (minutes > 0) {
124      stringBuilder.append(minutes);
125      stringBuilder.append(MINUTE);
126    }
127    return stringBuilder.toString();
128  }
129
130  /**
131   * Return the duration in minutes.
132   * <br>
133   * For instance, Duration.decode(1h, 24).toMinutes() will return 60.
134   */
135  public long toMinutes() {
136    return durationInMinutes;
137  }
138
139  /**
140   * Return true if the given duration is greater than the current one.
141   */
142  public boolean isGreaterThan(Duration other) {
143    return toMinutes() > other.toMinutes();
144  }
145
146  /**
147   * Add the given duration to the current one.
148   */
149  public Duration add(Duration with) {
150    return Duration.create(durationInMinutes + with.durationInMinutes);
151  }
152
153  /**
154   * Subtract the given duration to the current one.
155   */
156  public Duration subtract(Duration with) {
157    return Duration.create(durationInMinutes - with.durationInMinutes);
158  }
159
160  /**
161   * Multiply the duration with the given factor.
162   */
163  public Duration multiply(int factor) {
164    return Duration.create(durationInMinutes * factor);
165  }
166
167  private static IllegalArgumentException invalid(String text, @Nullable Exception e) {
168    throw new IllegalArgumentException(String.format("Duration '%s' is invalid, it should use the following sample format : 2d 10h 15min", text), e);
169  }
170
171  @Override
172  public boolean equals(Object o) {
173    if (this == o) {
174      return true;
175    }
176    if (o == null || getClass() != o.getClass()) {
177      return false;
178    }
179
180    Duration that = (Duration) o;
181    return durationInMinutes == that.durationInMinutes;
182
183  }
184
185  @Override
186  public int hashCode() {
187    return (int) (durationInMinutes ^ (durationInMinutes >>> 32));
188  }
189
190  @Override
191  public String toString() {
192    return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
193  }
194}