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