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
18package com.android.mms.dom.smil;
19
20import org.w3c.dom.DOMException;
21import org.w3c.dom.Element;
22import org.w3c.dom.smil.Time;
23
24public 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     */
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     */
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     */
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
234    public boolean getBaseBegin() {
235        // TODO Auto-generated method stub
236        return false;
237    }
238
239    public Element getBaseElement() {
240        // TODO Auto-generated method stub
241        return null;
242    }
243
244    public String getEvent() {
245        // TODO Auto-generated method stub
246        return null;
247    }
248
249    public String getMarker() {
250        // TODO Auto-generated method stub
251        return null;
252    }
253
254    public double getOffset() {
255        // TODO Auto-generated method stub
256        return 0;
257    }
258
259    public boolean getResolved() {
260        return mResolved;
261    }
262
263    public double getResolvedOffset() {
264        return mResolvedOffset;
265    }
266
267    public short getTimeType() {
268        return mTimeType;
269    }
270
271    public void setBaseBegin(boolean baseBegin) throws DOMException {
272        // TODO Auto-generated method stub
273
274    }
275
276    public void setBaseElement(Element baseElement) throws DOMException {
277        // TODO Auto-generated method stub
278
279    }
280
281    public void setEvent(String event) throws DOMException {
282        // TODO Auto-generated method stub
283
284    }
285
286    public void setMarker(String marker) throws DOMException {
287        // TODO Auto-generated method stub
288
289    }
290
291    public void setOffset(double offset) throws DOMException {
292        // TODO Auto-generated method stub
293
294    }
295}
296