1 /*
2  * Copyright (C) 2007-2008 Esmertec AG.
3  * Copyright (C) 2007-2008 The Android Open Source Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.mms.dom.smil;
19 
20 import org.w3c.dom.DOMException;
21 import org.w3c.dom.Element;
22 import org.w3c.dom.smil.Time;
23 
24 public class TimeImpl implements Time {
25     static final int ALLOW_INDEFINITE_VALUE = (1 << 0);
26     static final int ALLOW_OFFSET_VALUE     = (1 << 1);
27     static final int ALLOW_SYNCBASE_VALUE   = (1 << 2);
28     static final int ALLOW_SYNCTOPREV_VALUE = (1 << 3);
29     static final int ALLOW_EVENT_VALUE      = (1 << 4);
30     static final int ALLOW_MARKER_VALUE     = (1 << 5);
31     static final int ALLOW_WALLCLOCK_VALUE  = (1 << 6);
32     static final int ALLOW_NEGATIVE_VALUE   = (1 << 7);
33     static final int ALLOW_ALL              = 0xFF;
34 
35     short mTimeType;
36     boolean mResolved;
37     double mResolvedOffset;
38 
39     /**
40      * Creates a TimeImpl representation of a time-value represented as a String.
41      * Time-values have the following syntax:
42      * <p>
43      * <pre>
44      * Time-val ::= ( smil-1.0-syncbase-value
45      *                          | "indefinite"
46      *                          | offset-value
47      *                          | syncbase-value
48      *                          | syncToPrev-value
49      *                          | event-value
50      *                          | media-marker-value
51      *                          | wallclock-sync-value )
52      * Smil-1.0-syncbase-value ::=
53      *          "id(" id-ref ")" ( "(" ( "begin" | "end" | clock-value ) ")" )?
54      * Offset-value         ::= ( "+" | "-" )? clock-value
55      * Syncbase-value       ::= ( id-ref "." ( "begin" | "end" ) ) ( ( "+" | "-" ) clock-value )?
56      * SyncToPrev-value     ::= ( "prev.begin" | "prev.end" ) ( ( "+" | "-" ) clock-value )?
57      * Event-value          ::= ( id-ref "." )? ( event-ref  ) ( ( "+" | "-" ) clock-value )?
58      * Media-marker-value   ::= id-ref ".marker(" marker-name ")"
59      * Wallclock-sync-value ::= "wallclock(" wallclock-value ")"
60      * </pre>
61      *
62      * @param timeValue A String in the representation specified above
63      * @param constraints Any combination of the #ALLOW_* flags
64      * @return  A TimeImpl instance representing
65      * @exception java.lang.IllegalArgumentException if the timeValue input
66      *          parameter does not comply with the defined syntax
67      * @exception java.lang.NullPointerException if the timekValue string is
68      *          <code>null</code>
69      */
TimeImpl(String timeValue, int constraints)70     TimeImpl(String timeValue, int constraints) {
71         /*
72          * We do not support yet:
73          *      - smil-1.0-syncbase-value
74          *      - syncbase-value
75          *      - syncToPrev-value
76          *      - event-value
77          *      - Media-marker-value
78          *      - Wallclock-sync-value
79          */
80         // Will throw NullPointerException if timeValue is null
81         if (timeValue.equals("indefinite")
82                 && ((constraints & ALLOW_INDEFINITE_VALUE) != 0) ) {
83             mTimeType = SMIL_TIME_INDEFINITE;
84         } else if ((constraints & ALLOW_OFFSET_VALUE) != 0) {
85             int sign = 1;
86             if (timeValue.startsWith("+")) {
87                 timeValue = timeValue.substring(1);
88             } else if (timeValue.startsWith("-")) {
89                 timeValue = timeValue.substring(1);
90                 sign = -1;
91             }
92             mResolvedOffset = sign*parseClockValue(timeValue)/1000.0;
93             mResolved = true;
94             mTimeType = SMIL_TIME_OFFSET;
95         } else {
96             throw new IllegalArgumentException("Unsupported time value");
97         }
98     }
99 
100     /**
101      * Converts a String representation of a clock value into the float
102      * representation used in this API.
103      * <p>
104      * Clock values have the following syntax:
105      * </p>
106      * <p>
107      * <pre>
108      * Clock-val         ::= ( Full-clock-val | Partial-clock-val | Timecount-val )
109      * Full-clock-val    ::= Hours ":" Minutes ":" Seconds ("." Fraction)?
110      * Partial-clock-val ::= Minutes ":" Seconds ("." Fraction)?
111      * Timecount-val     ::= Timecount ("." Fraction)? (Metric)?
112      * Metric            ::= "h" | "min" | "s" | "ms"
113      * Hours             ::= DIGIT+; any positive number
114      * Minutes           ::= 2DIGIT; range from 00 to 59
115      * Seconds           ::= 2DIGIT; range from 00 to 59
116      * Fraction          ::= DIGIT+
117      * Timecount         ::= DIGIT+
118      * 2DIGIT            ::= DIGIT DIGIT
119      * DIGIT             ::= [0-9]
120      * </pre>
121      *
122      * @param clockValue A String in the representation specified above
123      * @return  A float value in milliseconds that matches the string
124      *          representation given as the parameter
125      * @exception java.lang.IllegalArgumentException if the clockValue input
126      *          parameter does not comply with the defined syntax
127      * @exception java.lang.NullPointerException if the clockValue string is
128      *          <code>null</code>
129      */
parseClockValue(String clockValue)130     public static float parseClockValue(String clockValue) {
131         try {
132             float result = 0;
133 
134             // Will throw NullPointerException if clockValue is null
135             clockValue = clockValue.trim();
136 
137             // Handle first 'Timecount-val' cases with metric
138             if (clockValue.endsWith("ms")) {
139                 result = parseFloat(clockValue, 2, true);
140             } else if (clockValue.endsWith("s")) {
141                 result = 1000*parseFloat(clockValue, 1, true);
142             } else if (clockValue.endsWith("min")) {
143                 result = 60000*parseFloat(clockValue, 3, true);
144             } else if (clockValue.endsWith("h")) {
145                 result = 3600000*parseFloat(clockValue, 1, true);
146             } else {
147                 // Handle Timecount-val without metric
148                 try {
149                     return parseFloat(clockValue, 0, true) * 1000;
150                 } catch (NumberFormatException _) {
151                     // Ignore
152                 }
153 
154                 // Split in {[Hours], Minutes, Seconds}
155                 String[] timeValues = clockValue.split(":");
156 
157                 // Read Hours if present and remember location of Minutes
158                 int indexOfMinutes;
159                 if (timeValues.length == 2) {
160                     indexOfMinutes = 0;
161                 } else if (timeValues.length == 3) {
162                     result = 3600000*(int)parseFloat(timeValues[0], 0, false);
163                     indexOfMinutes = 1;
164                 } else {
165                     throw new IllegalArgumentException();
166                 }
167 
168                 // Read Minutes
169                 int minutes = (int)parseFloat(timeValues[indexOfMinutes], 0, false);
170                 if ((minutes >= 00) && (minutes <= 59)) {
171                     result += 60000*minutes;
172                 } else {
173                     throw new IllegalArgumentException();
174                 }
175 
176                 // Read Seconds
177                 float seconds = parseFloat(timeValues[indexOfMinutes + 1], 0, true);
178                 if ((seconds >= 00) && (seconds < 60)) {
179                     result += 60000*seconds;
180                 } else {
181                     throw new IllegalArgumentException();
182                 }
183 
184             }
185             return result;
186         } catch (NumberFormatException e) {
187             throw new IllegalArgumentException();
188         }
189     }
190 
191     /**
192      * Parse a value formatted as follows:
193      * <p>
194      * <pre>
195      * Value    ::= Number ("." Decimal)? (Text)?
196      * Number   ::= DIGIT+; any positive number
197      * Decimal  ::= DIGIT+; any positive number
198      * Text     ::= CHAR*;   any sequence of chars
199      * DIGIT    ::= [0-9]
200      * </pre>
201      * @param value The Value to parse
202      * @param ignoreLast The size of Text to ignore
203      * @param parseDecimal Whether Decimal is expected
204      * @return The float value without Text, rounded to 3 digits after '.'
205      * @throws IllegalArgumentException if Decimal was not expected but encountered
206      */
parseFloat(String value, int ignoreLast, boolean parseDecimal)207     private static float parseFloat(String value, int ignoreLast, boolean parseDecimal) {
208         // Ignore last characters
209         value = value.substring(0, value.length() - ignoreLast);
210 
211         float result;
212         int indexOfComma = value.indexOf('.');
213         if (indexOfComma != -1) {
214             if (!parseDecimal) {
215                 throw new IllegalArgumentException("int value contains decimal");
216             }
217             // Ensure that there are at least 3 decimals
218             value = value + "000";
219             // Read value up to 3 decimals and cut the rest
220             result = Float.parseFloat(value.substring(0, indexOfComma));
221             result += Float.parseFloat(
222                     value.substring(indexOfComma + 1, indexOfComma + 4))/1000;
223         } else {
224             result = Integer.parseInt(value);
225         }
226 
227         return result;
228     }
229 
230     /*
231      * Time Interface
232      */
233 
getBaseBegin()234     public boolean getBaseBegin() {
235         // TODO Auto-generated method stub
236         return false;
237     }
238 
getBaseElement()239     public Element getBaseElement() {
240         // TODO Auto-generated method stub
241         return null;
242     }
243 
getEvent()244     public String getEvent() {
245         // TODO Auto-generated method stub
246         return null;
247     }
248 
getMarker()249     public String getMarker() {
250         // TODO Auto-generated method stub
251         return null;
252     }
253 
getOffset()254     public double getOffset() {
255         // TODO Auto-generated method stub
256         return 0;
257     }
258 
getResolved()259     public boolean getResolved() {
260         return mResolved;
261     }
262 
getResolvedOffset()263     public double getResolvedOffset() {
264         return mResolvedOffset;
265     }
266 
getTimeType()267     public short getTimeType() {
268         return mTimeType;
269     }
270 
setBaseBegin(boolean baseBegin)271     public void setBaseBegin(boolean baseBegin) throws DOMException {
272         // TODO Auto-generated method stub
273 
274     }
275 
setBaseElement(Element baseElement)276     public void setBaseElement(Element baseElement) throws DOMException {
277         // TODO Auto-generated method stub
278 
279     }
280 
setEvent(String event)281     public void setEvent(String event) throws DOMException {
282         // TODO Auto-generated method stub
283 
284     }
285 
setMarker(String marker)286     public void setMarker(String marker) throws DOMException {
287         // TODO Auto-generated method stub
288 
289     }
290 
setOffset(double offset)291     public void setOffset(double offset) throws DOMException {
292         // TODO Auto-generated method stub
293 
294     }
295 }
296