    001/*
    002 * Copyright (c) 2007-2017 Xplenty, 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
    021package cascading.operation.text;
    022
    023import java.beans.ConstructorProperties;
    024import java.text.ParseException;
    025import java.text.SimpleDateFormat;
    026import java.util.Arrays;
    027import java.util.Calendar;
    028import java.util.Date;
    029import java.util.Locale;
    030import java.util.TimeZone;
    031
    032import cascading.flow.FlowProcess;
    033import cascading.operation.Function;
    034import cascading.operation.FunctionCall;
    035import cascading.operation.OperationException;
    036import cascading.tuple.Fields;
    037import cascading.tuple.Tuple;
    038import cascading.util.Pair;
    039
    040/**
    041 * Class DateParser is used to convert a text date string to a timestamp, the number of milliseconds
    042 * since January 1, 1970, 00:00:00 GMT, using the {@link SimpleDateFormat} syntax.
    043 * <p/>
    044 * If given, individual {@link Calendar} fields can be stored in unique fields for a given {@link TimeZone} and {@link Locale}.
    045 */
    046public class DateParser extends DateOperation implements Function<Pair<SimpleDateFormat, Tuple>>
    047  {
    048  /** Field FIELD_NAME */
    049  public static final String FIELD_NAME = "ts";
    050
    051  /** Field calendarFields */
    052  private int[] calendarFields;
    053
    054  /**
    055   * Constructor DateParser creates a new DateParser instance that creates a simple long time stamp of the parsed date.
    056   *
    057   * @param dateFormatString of type String
    058   */
    059  @ConstructorProperties({"dateFormatString"})
    060  public DateParser( String dateFormatString )
    061    {
    062    super( 1, new Fields( FIELD_NAME ), dateFormatString );
    063    }
    064
    065  /**
    066   * Constructor DateParser creates a new DateParser instance.
    067   *
    068   * @param fieldDeclaration of type Fields
    069   * @param dateFormatString of type String
    070   */
    071  @ConstructorProperties({"fieldDeclaration", "dateFormatString"})
    072  public DateParser( Fields fieldDeclaration, String dateFormatString )
    073    {
    074    super( 1, fieldDeclaration, dateFormatString );
    075    }
    076
    077  /**
    078   * Constructor DateParser creates a new DateParser instance, where calendarFields is an int[] of {@link Calendar} field
    079   * values. See {@link Calendar#get(int)}.
    080   *
    081   * @param fieldDeclaration of type Fields
    082   * @param calendarFields   of type int[]
    083   * @param dateFormatString of type String
    084   */
    085  @ConstructorProperties({"fieldDeclaration", "calendarFields", "dateFormatString"})
    086  public DateParser( Fields fieldDeclaration, int[] calendarFields, String dateFormatString )
    087    {
    088    this( fieldDeclaration, calendarFields, null, null, dateFormatString );
    089    }
    090
    091  /**
    092   * Constructor DateParser creates a new DateParser instance, where zone and locale are passed to the internal
    093   * {@link SimpleDateFormat} instance.
    094   *
    095   * @param fieldDeclaration of type Fields
    096   * @param zone             of type TimeZone
    097   * @param locale           of type Locale
    098   * @param dateFormatString of type String
    099   */
    100  @ConstructorProperties({"fieldDeclaration", "zone", "locale", "dateFormatString"})
    101  public DateParser( Fields fieldDeclaration, TimeZone zone, Locale locale, String dateFormatString )
    102    {
    103    this( fieldDeclaration, null, zone, locale, dateFormatString );
    104    }
    105
    106  /**
    107   * Constructor DateParser creates a new DateParser instance, where calendarFields is an int[] of {@link Calendar} field
    108   * values. See {@link Calendar#get(int)}. The {@link TimeZone} and/or {@link Locale} may also be set.
    109   *
    110   * @param fieldDeclaration of type Fields
    111   * @param calendarFields   of type int[]
    112   * @param zone             of type TimeZone
    113   * @param locale           of type Locale
    114   * @param dateFormatString of type String
    115   */
    116  @ConstructorProperties({"fieldDeclaration", "calendarFields", "zone", "locale", "dateFormatString"})
    117  public DateParser( Fields fieldDeclaration, int[] calendarFields, TimeZone zone, Locale locale, String dateFormatString )
    118    {
    119    super( 1, fieldDeclaration, dateFormatString, zone, locale );
    120
    121    if( calendarFields != null )
    122      {
    123      this.calendarFields = Arrays.copyOf( calendarFields, calendarFields.length );
    124
    125      if( fieldDeclaration.size() != calendarFields.length )
    126        throw new IllegalArgumentException( "fieldDeclaration must be same size as calendarFields, was " + fieldDeclaration.print() + " with calendar size: " + calendarFields.length );
    127      }
    128    else
    129      {
    130      if( !fieldDeclaration.isSubstitution() && fieldDeclaration.size() != 1 )
    131        throw new IllegalArgumentException( "fieldDeclaration may only declare one field name, got " + fieldDeclaration.print() );
    132      }
    133    }
    134
    135  @Override
    136  public void operate( FlowProcess flowProcess, FunctionCall<Pair<SimpleDateFormat, Tuple>> functionCall )
    137    {
    138    Tuple output = functionCall.getContext().getRhs();
    139
    140    try
    141      {
    142      String value = functionCall.getArguments().getString( 0 );
    143
    144      if( value == null ) // if null, return null for the field
    145        {
    146        output.set( 0, null ); // safe to call set, tuple is size of 1
    147
    148        functionCall.getOutputCollector().add( output );
    149
    150        return;
    151        }
    152
    153      Date date = functionCall.getContext().getLhs().parse( value );
    154
    155      if( calendarFields == null )
    156        output.set( 0, date.getTime() ); // safe to call set, tuple is size of 1
    157      else
    158        makeCalendarFields( output, date );
    159      }
    160    catch( ParseException exception )
    161      {
    162      throw new OperationException( "unable to parse input value: " + functionCall.getArguments().getObject( 0 ), exception );
    163      }
    164
    165    functionCall.getOutputCollector().add( output );
    166    }
    167
    168  private void makeCalendarFields( Tuple output, Date date )
    169    {
    170    output.clear();
    171
    172    Calendar calendar = getCalendar();
    173    calendar.setTime( date );
    174
    175    for( int i = 0; i < calendarFields.length; i++ )
    176    //noinspection MagicConstant
    177      output.add( calendar.get( calendarFields[ i ] ) );
    178    }
    179
    180  @Override
    181  public boolean equals( Object object )
    182    {
    183    if( this == object )
    184      return true;
    185    if( !( object instanceof DateParser ) )
    186      return false;
    187    if( !super.equals( object ) )
    188      return false;
    189
    190    DateParser that = (DateParser) object;
    191
    192    if( !Arrays.equals( calendarFields, that.calendarFields ) )
    193      return false;
    194
    195    return true;
    196    }
    197
    198  @Override
    199  public int hashCode()
    200    {
    201    int result = super.hashCode();
    202    result = 31 * result + ( calendarFields != null ? Arrays.hashCode( calendarFields ) : 0 );
    203    return result;
    204    }
    205  }