PageRenderTime 93ms CodeModel.GetById 68ms app.highlight 22ms RepoModel.GetById 1ms app.codeStats 0ms

/protocols/smpp/src/main/java/org/mobicents/protocols/smpp/util/SMPPDateFormat.java

http://mobicents.googlecode.com/
Java | 319 lines | 182 code | 17 blank | 120 comment | 28 complexity | 52ae452794e0854a5e642ded793fb8bd MD5 | raw file
  1/*
  2 * JBoss, Home of Professional Open Source
  3 * Copyright 2011, Red Hat, Inc. and individual contributors
  4 * by the @authors tag. See the copyright.txt in the distribution for a
  5 * full listing of individual contributors.
  6 *
  7 * This is free software; you can redistribute it and/or modify it
  8 * under the terms of the GNU Lesser General Public License as
  9 * published by the Free Software Foundation; either version 2.1 of
 10 * the License, or (at your option) any later version.
 11 *
 12 * This software is distributed in the hope that it will be useful,
 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 15 * Lesser General Public License for more details.
 16 *
 17 * You should have received a copy of the GNU Lesser General Public
 18 * License along with this software; if not, write to the Free
 19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 21 */
 22
 23package org.mobicents.protocols.smpp.util;
 24
 25import java.text.FieldPosition;
 26import java.text.Format;
 27import java.text.MessageFormat;
 28import java.text.ParsePosition;
 29import java.util.Calendar;
 30import java.util.SimpleTimeZone;
 31import java.util.TimeZone;
 32import java.util.regex.Pattern;
 33
 34import org.mobicents.protocols.smpp.SMPPRuntimeException;
 35
 36/**
 37 * Parse a String to an SMPPDate object.
 38 * @version $Id: SMPPDateFormat.java 463 2009-06-16 12:07:19Z orank $
 39 */
 40public class SMPPDateFormat extends Format {
 41    private static final long serialVersionUID = 2L;
 42
 43    /**
 44     * "Local" time specification, no timezone info included.
 45     */
 46    private static final String ABS_FORMAT_12 =
 47        "{0,number,00}{1,number,00}{2,number,00}{3,number,00}{4,number,00}"
 48        + "{5,number,00}";
 49    /**
 50     * Absolute format, specifying timezone information.
 51     */
 52    private static final String ABS_FORMAT_16 =
 53        ABS_FORMAT_12 + "{6,number,0}{7,number,00}{8}";
 54    /**
 55     * Relative format.
 56     */
 57    private static final String REL_FORMAT_16 = ABS_FORMAT_12 + "000R";
 58    
 59    private static final Pattern ABS_PATTERN_16 = Pattern.compile("^\\d{15}[+-]");
 60    private static final Pattern REL_PATTERN_16 = Pattern.compile("^\\d{15}R");
 61    private static final Pattern ABS_PATTERN_12 = Pattern.compile("^\\d{12}");
 62
 63    /**
 64     * The default year modifier. The year modifier is the value used
 65     * to convert 2-digit years into their full version. Year modifier is
 66     * simply added to (or subtracted from) one form to get the other.
 67     * <br />
 68     * <code>
 69     * int twoDigitYear = fullYear - getYearModifier();<br />
 70     * int fullYear = twoDigitYear + getYearModifier();<br />
 71     * </code>
 72     * The default value for this is <code>2000</code>, meaning the allowed
 73     * values for a 2-digit year (0 to 99) represent the years 2000 to 2099.
 74     */
 75    public static final int DEFAULT_YEAR_MODIFIER = 2000;
 76    
 77    private int yearModifier = DEFAULT_YEAR_MODIFIER;
 78    
 79    public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
 80        if (toAppendTo == null || pos == null) {
 81            throw new NullPointerException();
 82        }
 83        if (obj == null) {
 84            return toAppendTo;
 85        }
 86        if (!(obj instanceof SMPPDate)) {
 87            throw new IllegalArgumentException("Cannot format an object of type "
 88                    + obj.getClass().getName());
 89        }
 90        SMPPDate date = (SMPPDate) obj;
 91        int year = date.getYear();
 92        String format = ABS_FORMAT_16;
 93        if (date.isAbsolute()) {
 94            year -= yearModifier;
 95            if (!date.hasTimezone()) {
 96                format = ABS_FORMAT_12;
 97            }
 98        } else if (date.isRelative()) {
 99            format = REL_FORMAT_16;
100        }
101        Object[] args = new Object[] {
102                Integer.valueOf(year),
103                Integer.valueOf(date.getMonth()),
104                Integer.valueOf(date.getDay()),
105                Integer.valueOf(date.getHour()),
106                Integer.valueOf(date.getMinute()),
107                Integer.valueOf(date.getSecond()),
108                Integer.valueOf(date.getTenth()),
109                Integer.valueOf(date.getUtcOffset()),
110                Character.valueOf(date.getSign()),
111        };
112        toAppendTo.append(MessageFormat.format(format, args));
113        return toAppendTo;
114    }
115
116    /**
117     * Parse a string into an {@link SMPPDate}.
118     * @param source The string to parse.
119     * @param pos The position to begin parsing from.
120     * @throws NullPointerException if <code>source</code> is <code>null</code>.
121     * @throws IllegalArgumentException if any of the parsed values exceed
122     * their allowed range.
123     */
124    public Object parseObject(String source, ParsePosition pos) {
125        String s = source;
126        if (pos.getIndex() > 0) {
127            s = source.substring(pos.getIndex());
128        }
129        int updatePos = 0;
130        boolean absolute = false;
131        boolean hasTz = true;
132        if (ABS_PATTERN_16.matcher(s).find()) {
133            absolute = true;
134            updatePos = 16;
135        } else if (REL_PATTERN_16.matcher(s).find()) {
136            absolute = false;
137            updatePos = 16;
138        } else if (ABS_PATTERN_12.matcher(s).find()) {
139            absolute = true;
140            hasTz = false;
141            updatePos = 12;
142        } else {
143            return null;
144        }
145        SMPPDate date = null;
146        try {
147            if (absolute) {
148                date = parseAbsoluteDate(s, pos, hasTz);
149            } else {
150                date = parseRelativeDate(s, pos);
151            }
152            pos.setIndex(pos.getIndex() + updatePos);
153        } catch (SMPPRuntimeException x) {
154            // Fall-through...ParsePosition will not get updated, and
155            // error index should have been set.
156        }
157        return date;
158    }
159
160    /**
161     * Get the value for the year modifier.
162     * @return The current value of the year modifier.
163     * @see #DEFAULT_YEAR_MODIFIER
164     */
165    public int getYearModifier() {
166        return yearModifier;
167    }
168
169    /**
170     * Set the value for the year modifier.
171     * @param yearModifier The new value for year modifier.
172     * @see #DEFAULT_YEAR_MODIFIER
173     * @see #getYearModifier()
174     */
175    public void setYearModifier(int yearModifier) {
176        this.yearModifier = yearModifier;
177    }
178
179    /**
180     * Parse an absolute date from the string.
181     * @param s The string to parse from.
182     * @param pos The parse position.
183     * @param hasTz Parse timezone information if <code>true</code>.
184     * @return The parsed SMPP date.
185     * @throws SMPPRuntimeException If the date cannot be parsed.
186     */
187    private SMPPDate parseAbsoluteDate(String s, ParsePosition pos, boolean hasTz) {
188        int index = pos.getIndex();
189        int year = parseAndCheck(s, pos, index, index + 2, 0, 99);
190        int month = parseAndCheck(s, pos, index + 2, index + 4, 1, 12);
191        int day = parseAndCheck(s, pos, index + 4, index + 6, 1, 31);
192        int hour = parseAndCheck(s, pos, index + 6, index + 8, 0, 23);
193        int minute = parseAndCheck(s, pos, index + 8, index + 10, 0, 59);
194        int second = parseAndCheck(s, pos, index + 10, index + 12, 0, 59);
195        Calendar cal;
196        if (hasTz) {
197            char sign = s.charAt(index + 15);
198            int tenth = parseAndCheck(s, pos, index + 12, index + 13, 0, 9);
199            int utcOffset = parseAndCheck(
200                    s, pos, index + 13, index + 15, 0, 48);
201            cal = getCalendar(year, month, day, hour, minute, second,
202                    tenth, utcOffset, sign);
203        } else {
204            cal = getCalendar(
205                    year, month, day, hour, minute, second, 0, 0, (char) 0);
206        }
207        return SMPPDate.getAbsoluteInstance(cal, hasTz);
208    }
209
210    /**
211     * Parse a relative date from the string.
212     * @param s The string to parse the date from.
213     * @param pos The parse position.
214     * @return The parsed SMPP date.
215     * @throws SMPPRuntimeException If the date cannot be parsed.
216     */
217    private SMPPDate parseRelativeDate(String s, ParsePosition pos) {
218        int index = pos.getIndex();
219        int year = parseAndCheck(s, pos, index, index + 2, 0, 99);
220        int month = parseAndCheck(s, pos, index + 2, index + 4, 0, 99);
221        int day = parseAndCheck(s, pos, index + 4, index + 6, 0, 99);
222        int hour = parseAndCheck(s, pos, index + 6, index + 8, 0, 99);
223        int minute = parseAndCheck(s, pos, index + 8, index + 10, 0, 99);
224        int second = parseAndCheck(s, pos, 10, 12, 0, 99);
225        return SMPPDate.getRelativeInstance(
226                year, month, day, hour, minute, second);
227    }
228    
229    /**
230     * Get a {@link java.util.Calendar} object, initialising its fields
231     * to the supplied values. The values should be specified in their
232     * SMPP form (that is, year is 0-99, month is 1-12) and this method will
233     * convert them appropriately before placing them in the calendar.
234     * @param year The year.
235     * @param month The month.
236     * @param day The day.
237     * @param hour The hour.
238     * @param minute The minute.
239     * @param second The second.
240     * @param tenths Tenths of a second.
241     * @param utcOffset The offset from UTC.
242     * @param sign The direction of the UTC offset ('+' or '-').
243     * @return An initialised Calendar object.
244     */
245    private Calendar getCalendar(int year,
246            int month,
247            int day,
248            int hour,
249            int minute,
250            int second,
251            int tenths,
252            int utcOffset,
253            char sign) {
254        Calendar calendar = Calendar.getInstance();
255        if (sign != (char) 0) {
256            calendar.setTimeZone(getTimeZoneForOffset(utcOffset, sign));
257        }
258        calendar.set(Calendar.YEAR, year + yearModifier);
259        calendar.set(Calendar.MONTH, month - 1);
260        calendar.set(Calendar.DAY_OF_MONTH, day);
261        calendar.set(Calendar.HOUR_OF_DAY, hour);
262        calendar.set(Calendar.MINUTE, minute);
263        calendar.set(Calendar.SECOND, second);
264        calendar.set(Calendar.MILLISECOND, tenths * 100);
265        return calendar;
266    }
267
268    /**
269     * Get a timezone for the specified UTC offset.
270     * A new {@link SimpleTimeZone} will be created and given the
271     * specified offset from UTC.
272     * @param utcOffset The offset, in quarter hours, from UTC.
273     * @param sign Whether the offset is ahead ('+') or behind ('-') UTC.
274     * @return A timezone object.
275     */
276    private TimeZone getTimeZoneForOffset(int utcOffset, char sign) {
277        int rawOffset = utcOffset * 900000;
278        if (sign == '-') {
279            rawOffset = -rawOffset;
280        }
281        int hours = utcOffset / 4;
282        int minutes = (utcOffset - (hours * 4)) * 15;
283        String id = String.format("UTC%c%02d:%02d", sign, hours, minutes);
284        TimeZone tz = new SimpleTimeZone(rawOffset, id);
285        return tz;
286    }
287    
288    /**
289     * Parse an integer from a string and verify the value lies within
290     * a maximum and minimum range. If it does
291     * not, update the <code>ParsePosition&apos;s</code> error index to point
292     * to <code>start</code> and throw an <code>SMPPRuntimeException</code>.
293     * <code>SMPPRuntimeException</code> is also thrown in the case of a
294     * <code>NumberFormatException</code>.
295     * @param s The string to parse the integer from.
296     * @param pos The parse position to update in case of error.
297     * @param start The start of the substring containing the integer.
298     * @param end The end of the substring containing the integer.
299     * @param min The minimum allowed value.
300     * @param max The maximum allowed value.
301     */
302    private int parseAndCheck(String s,
303            ParsePosition pos,
304            int start,
305            int end,
306            int min,
307            int max) {
308        try {
309            int n = Integer.parseInt(s.substring(start, end));
310            if (n < min || n > max) {
311                throw new NumberFormatException();
312            }
313            return n;
314        } catch (NumberFormatException x) {
315            pos.setErrorIndex(start);
316            throw new SMPPRuntimeException();
317        }
318    }
319}