View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.convert;
21  
22  import org.apache.myfaces.tobago.component.Attributes;
23  import org.apache.myfaces.tobago.util.ComponentUtils;
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  
27  import javax.faces.component.UIComponent;
28  import javax.faces.context.FacesContext;
29  import javax.faces.convert.Converter;
30  import javax.faces.convert.ConverterException;
31  import java.lang.invoke.MethodHandles;
32  import java.text.DecimalFormat;
33  import java.text.NumberFormat;
34  import java.util.ArrayList;
35  import java.util.List;
36  import java.util.StringTokenizer;
37  
38  /**
39   * Converts durations. The duration value in the model is of type long.
40   * The string format must have one of this patterns:
41   * <ul>
42   * <li>hh:MM:ss</li>
43   * <li>MM:ss</li>
44   * <li>ss</li>
45   * </ul>
46   * There may be an optional attribute "unit" in the component, which allows to set the unit of the model.
47   * The default unit is "millis".
48   * <table>
49   *   <caption>Examples</caption>
50   *   <tr><th>input string</th><th>unit</th><th>resulting long in model</th><th>Remark</th></tr>
51   *   <tr><td>1:15</td><td>milli</td><td>75000</td><td></td></tr>
52   *   <tr><td>1:15:00</td><td>hour</td><td>1</td><td>Loosing 15 Minutes!</td></tr>
53   *   <tr><td>1:15:00</td><td>null</td><td>4500000</td><td></td></tr>
54   * </table>
55   */
56  @org.apache.myfaces.tobago.apt.annotation.Converter(id = DurationConverter.CONVERTER_ID)
57  public class DurationConverter implements Converter {
58  
59    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
60  
61    public static final String CONVERTER_ID = "org.apache.myfaces.tobago.Duration";
62  
63    private enum Unit {
64      nano, milli, second, minute, hour, day, year
65    }
66  
67    @Override
68    public String getAsString(
69        final FacesContext facesContext, final UIComponent component, final Object object)
70        throws ConverterException {
71      if (object == null || object instanceof String) {
72        return (String) object;
73      }
74      double aDouble = ((Number) object).doubleValue();
75      boolean negative = false;
76      if (aDouble < 0) {
77        negative = true;
78        aDouble = -aDouble;
79      }
80      final double factor = getUnitFactor(component);
81      aDouble = aDouble * factor;
82  
83      final NumberFormat format = new DecimalFormat("00");
84      long value = Double.valueOf(aDouble).longValue();
85      final int seconds = (int) (value % 60);
86      value = value / 60;
87      final int minutes = (int) (value % 60);
88      value = value / 60;
89      final String string;
90      if (value > 0) {
91        string = (negative ? "-" : "") + value + ":"
92            + format.format(minutes) + ":"
93            + format.format(seconds);
94      } else {
95        string = (negative ? "-" : "") + minutes + ":"
96            + format.format(seconds);
97      }
98      LOG.debug("string = '{}'", string);
99      return string;
100   }
101 
102   @Override
103   public Object getAsObject(
104       final FacesContext facesContext, final UIComponent component, final String string)
105       throws ConverterException {
106     final boolean negative = string.indexOf('-') > -1;
107     final StringTokenizer tokenizer = new StringTokenizer(string, " :-");
108     final List<String> elements = new ArrayList<>();
109     while (tokenizer.hasMoreTokens()) {
110       elements.add(tokenizer.nextToken());
111     }
112     final int hours;
113     final int minutes;
114     final int seconds;
115     switch (elements.size()) {
116       case 3:
117         hours = Integer.parseInt(elements.get(0));
118         minutes = Integer.parseInt(elements.get(1));
119         seconds = Integer.parseInt(elements.get(2));
120         break;
121       case 2:
122         hours = 0;
123         minutes = Integer.parseInt(elements.get(0));
124         seconds = Integer.parseInt(elements.get(1));
125         break;
126       case 1:
127         hours = 0;
128         minutes = 0;
129         seconds = Integer.parseInt(elements.get(0));
130         break;
131       default:
132         throw new ConverterException("Cannot parse string='" + string + "'");
133     }
134     final double factor = getUnitFactor(component);
135     final long value = (long) (((hours * 60L + minutes) * 60L + seconds) / factor);
136     if (negative) {
137       return -value;
138     } else {
139       return value;
140     }
141   }
142 
143   private static double getUnitFactor(final UIComponent component) {
144     final String unitString = ComponentUtils.getStringAttribute(component, Attributes.unit);
145     Unit unit;
146     if (unitString != null) {
147       try {
148         unit = Unit.valueOf(unitString);
149       } catch (final Exception e) {
150         LOG.warn("Unsupported unit: '{}'", unitString);
151         unit = Unit.milli;
152       }
153     } else {
154       unit = Unit.milli;
155     }
156     switch (unit) {
157       case nano:
158         return 0.000_000_001;
159       default:
160       case milli:
161         return 0.001;
162       case second:
163         return 1.0;
164       case minute:
165         return 60.0;
166       case hour:
167         return 3_600.0;
168       case day:
169         return 86_400.0;
170       case year:
171         return 31_556_736.0;
172     }
173   }
174 
175 }