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}