001    /*
002     * Copyright (c) 2007-2015 Concurrent, Inc. All Rights Reserved.
003     *
004     * Project and contact information: http://www.cascading.org/
005     *
006     * This file is part of the Cascading project.
007     *
008     * Licensed under the Apache License, Version 2.0 (the "License");
009     * you may not use this file except in compliance with the License.
010     * You may obtain a copy of the License at
011     *
012     *     http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing, software
015     * distributed under the License is distributed on an "AS IS" BASIS,
016     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017     * See the License for the specific language governing permissions and
018     * limitations under the License.
019     */
020    
021    package cascading.tuple.type;
022    
023    import java.lang.reflect.Type;
024    import java.text.ParseException;
025    import java.text.SimpleDateFormat;
026    import java.util.Calendar;
027    import java.util.Date;
028    import java.util.Locale;
029    import java.util.TimeZone;
030    
031    import cascading.CascadingException;
032    import cascading.util.Util;
033    
034    /**
035     * Class DateCoercibleType is an implementation of {@link CoercibleType}.
036     * <p/>
037     * Given a {@code dateFormatString}, using the {@link SimpleDateFormat} format, this CoercibleType
038     * will convert a value from the formatted string to a {@code Long} canonical type and back.
039     * <p/>
040     * This class when presented with a Long timestamp value will assume the value is in UTC.
041     * <p/>
042     * See {@link cascading.operation.text.DateParser} and {@link cascading.operation.text.DateFormatter} for similar
043     * Operations for use within a pipe assembly.
044     */
045    public class DateType implements CoercibleType<Long>
046      {
047      /** Field zone */
048      protected TimeZone zone;
049      /** Field locale */
050      protected Locale locale;
051      /** Field dateFormatString */
052      protected String dateFormatString;
053      /** Field dateFormat */
054      private transient SimpleDateFormat dateFormat;
055    
056      /**
057       * Create a new DateType instance.
058       *
059       * @param dateFormatString
060       * @param zone
061       * @param locale
062       */
063      public DateType( String dateFormatString, TimeZone zone, Locale locale )
064        {
065        this.zone = zone;
066        this.locale = locale;
067        this.dateFormatString = dateFormatString;
068        }
069    
070      public DateType( String dateFormatString, TimeZone zone )
071        {
072        this.zone = zone;
073        this.dateFormatString = dateFormatString;
074        }
075    
076      /**
077       * Create a new DateType instance.
078       *
079       * @param dateFormatString
080       */
081      public DateType( String dateFormatString )
082        {
083        this.dateFormatString = dateFormatString;
084        }
085    
086      @Override
087      public Class getCanonicalType()
088        {
089        return Long.TYPE;
090        }
091    
092      public SimpleDateFormat getDateFormat()
093        {
094        if( dateFormat != null )
095          return dateFormat;
096    
097        dateFormat = new SimpleDateFormat( dateFormatString, getLocale() );
098    
099        dateFormat.setTimeZone( getZone() );
100    
101        return dateFormat;
102        }
103    
104      private Locale getLocale()
105        {
106        if( locale != null )
107          return locale;
108    
109        return Locale.getDefault();
110        }
111    
112      private TimeZone getZone()
113        {
114        if( zone != null )
115          return zone;
116    
117        return TimeZone.getTimeZone( "UTC" );
118        }
119    
120      protected Calendar getCalendar()
121        {
122        return Calendar.getInstance( TimeZone.getTimeZone( "UTC" ), getLocale() );
123        }
124    
125      @Override
126      public Long canonical( Object value )
127        {
128        if( value == null )
129          return null;
130    
131        Class from = value.getClass();
132    
133        if( from == String.class )
134          return parse( (String) value ).getTime();
135    
136        if( from == Date.class )
137          return ( (Date) value ).getTime(); // in UTC
138    
139        if( from == Long.class || from == long.class )
140          return (Long) value;
141    
142        throw new CascadingException( "unknown type coercion requested from: " + Util.getTypeName( from ) );
143        }
144    
145      @Override
146      public Object coerce( Object value, Type to )
147        {
148        if( value == null )
149          return null;
150    
151        Class from = value.getClass();
152    
153        if( from != Long.class )
154          throw new IllegalStateException( "was not normalized" );
155    
156        // no coercion, or already in canonical form
157        if( to == Long.class || to == long.class || to == Object.class )
158          return value;
159    
160        if( to == String.class )
161          {
162          Calendar calendar = getCalendar();
163    
164          calendar.setTimeInMillis( (Long) value );
165    
166          return getDateFormat().format( calendar.getTime() );
167          }
168    
169        throw new CascadingException( "unknown type coercion requested, from: " + Util.getTypeName( from ) + " to: " + Util.getTypeName( to ) );
170        }
171    
172      private Date parse( String value )
173        {
174        try
175          {
176          return getDateFormat().parse( value );
177          }
178        catch( ParseException exception )
179          {
180          throw new CascadingException( "unable to parse value: " + value + " with format: " + dateFormatString );
181          }
182        }
183    
184      @Override
185      public String toString()
186        {
187        final StringBuilder sb = new StringBuilder( "DateType{" );
188        sb.append( "dateFormatString='" ).append( dateFormatString ).append( '\'' );
189        sb.append( '}' );
190        return sb.toString();
191        }
192      }