/*******************************************************************************
* Copyright © 2006, 2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*
*******************************************************************************/
package org.eclipse.edt.javart.util;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import org.eclipse.edt.javart.Constants;
import org.eclipse.edt.javart.messages.Message;
import org.eclipse.edt.runtime.java.eglx.lang.ETimestamp;
import eglx.lang.InvalidArgumentException;
/**
* Helper class used to calculate arguments to be passed when instantiating
* timestamp and interval types.
*/
public class TimestampIntervalMask implements Serializable
{
private static final long serialVersionUID = Constants.SERIAL_VERSION_UID;
/**
* The pattern associated with a timestamp or interval.
*/
private String pattern = "";
private transient String lowerCasePattern = pattern;
public TimestampIntervalMask(String pattern)
{
if ( pattern != null )
{
pattern = pattern.trim();
if (pattern.length() > 0) {
// make this big enough so that repeated characters don't cause an overflow in the array
int[] flags = new int[100];
int index = 0;
for ( int i = 0; i < pattern.length(); i++ )
{
switch ( pattern.charAt( i ) )
{
case 'y':
case 'Y':
flags[index++] = ETimestamp.YEAR_CODE;
break;
case 'm':
flags[index++] = ETimestamp.MINUTE_CODE;
break;
case 'M':
flags[index++] = ETimestamp.MONTH_CODE;
break;
case 'd':
case 'D':
flags[index++] = ETimestamp.DAY_CODE;
break;
case 'h':
case 'H':
flags[index++] = ETimestamp.HOUR_CODE;
break;
case 's':
case 'S':
flags[index++] = ETimestamp.SECOND_CODE;
break;
case 'f':
case 'F':
flags[index++] = ETimestamp.FRACTION1_CODE;
break;
default:
InvalidArgumentException ex = new InvalidArgumentException();
throw ex.fillInMessage(Message.INVALID_DATA, "yyyyMMddhhmmssffffff");
}
}
// make sure that from the start of the flag array to endIndex, the values are >= the current one
int endIndex = index - 1;
for (index = 0; index < endIndex; index++) {
// if the next value is the same or exactly 1 larger, then pattern is still ok
if (!(flags[index] == flags[index + 1] || (flags[index] + 1) == flags[index + 1])) {
InvalidArgumentException ex = new InvalidArgumentException();
throw ex.fillInMessage(Message.INVALID_DATA, "yyyyMMddhhmmssffffff");
}
}
// copy the valid pattern
this.pattern = pattern;
// Before we make the lowerCasePattern, replace 'S' with 'f' so we can
// tell the difference between 'S' and 's'. They'll both be 's' after
// conversion to lower case.
this.lowerCasePattern = pattern.replace( 'S', 'f' ).toLowerCase();
}
}
}
/**
* Get the start code.
*/
public int getStartCode()
{
if ( lowerCasePattern.length() > 0 )
{
switch ( lowerCasePattern.charAt( 0 ) )
{
case 'y':
return ETimestamp.YEAR_CODE;
case 'm':
return isMonth( true ) ? ETimestamp.MONTH_CODE : ETimestamp.MINUTE_CODE;
case 'd':
return ETimestamp.DAY_CODE;
case 'h':
return ETimestamp.HOUR_CODE;
case 's':
return ETimestamp.SECOND_CODE;
case 'f':
switch( numStartChars() )
{
case 1:
return ETimestamp.FRACTION1_CODE;
case 2:
return ETimestamp.FRACTION2_CODE;
case 3:
return ETimestamp.FRACTION3_CODE;
case 4:
return ETimestamp.FRACTION4_CODE;
case 5:
return ETimestamp.FRACTION5_CODE;
case 6:
return ETimestamp.FRACTION6_CODE;
}
}
}
return -1;
}
/**
* get the end code.
*/
public int getEndCode()
{
if ( lowerCasePattern.length() > 0 )
{
switch ( lowerCasePattern.charAt( lowerCasePattern.length() - 1 ) )
{
case 'y':
return ETimestamp.YEAR_CODE;
case 'm':
return isMonth( false ) ? ETimestamp.MONTH_CODE : ETimestamp.MINUTE_CODE;
case 'd':
return ETimestamp.DAY_CODE;
case 'h':
return ETimestamp.HOUR_CODE;
case 's':
return ETimestamp.SECOND_CODE;
case 'f':
switch( numEndChars() )
{
case 1:
return ETimestamp.FRACTION1_CODE;
case 2:
return ETimestamp.FRACTION2_CODE;
case 3:
return ETimestamp.FRACTION3_CODE;
case 4:
return ETimestamp.FRACTION4_CODE;
case 5:
return ETimestamp.FRACTION5_CODE;
case 6:
return ETimestamp.FRACTION6_CODE;
}
}
}
return -1;
}
/**
* Check if 'm' represents a month or not.
*/
private boolean isMonth( boolean isStartCode )
{
int count;
if( isStartCode )
{
count = numStartChars();
if( lowerCasePattern.length() == count )
{
// The pattern contains only 'm's, decide based on the case of first 'm'
if( pattern.charAt(0) == 'M' )
return true;
else
return false;
}
else
{
return ( lowerCasePattern.charAt(count) == 'd' );
}
}
else
{
count = numEndChars();
if( lowerCasePattern.length() == count )
{
// The pattern contains only 'm's, decide based on the case of first 'm'.
if( pattern.charAt(0) == 'M' )
return true;
else
return false;
}
else
{
return( lowerCasePattern.charAt(lowerCasePattern.length() - count - 1) == 'y' );
}
}
}
/**
* Number of start characters
*/
private int numStartChars()
{
int count = 1;
char prevCh = lowerCasePattern.charAt(0);
for( int i = 1; i < lowerCasePattern.length(); i++ )
{
if( lowerCasePattern.charAt(i) == prevCh )
{
prevCh = lowerCasePattern.charAt(i);
count++;
}
else
{
break;
}
}
return count;
}
/**
* Number of end characters
*/
private int numEndChars()
{
int count = 1;
char prevCh = lowerCasePattern.charAt( lowerCasePattern.length() - 1 );
for( int i = lowerCasePattern.length() - 2; i >= 0; i-- )
{
if (lowerCasePattern.charAt(i) == prevCh)
{
prevCh = lowerCasePattern.charAt(i);
count++;
}
else
{
break;
}
}
return count;
}
/**
* Get the formatting string.
*/
public String getFormattingPattern( int startCode, int endCode )
{
StringBuilder buf = new StringBuilder();
int idx = 0;
int length = lowerCasePattern.length();
switch( startCode )
{
case ETimestamp.YEAR_CODE:
for( ; idx < length && lowerCasePattern.charAt( idx ) == 'y'; idx++ )
{
buf.append('y');
}
if( endCode == ETimestamp.YEAR_CODE )
break;
case ETimestamp.MONTH_CODE:
if( startCode != ETimestamp.MONTH_CODE )
{
buf.append('-');
}
for( ; idx < length && lowerCasePattern.charAt( idx ) == 'm'; idx++ )
{
buf.append('M');
}
if( endCode == ETimestamp.MONTH_CODE )
break;
case ETimestamp.DAY_CODE:
if( startCode != ETimestamp.DAY_CODE )
{
buf.append('-');
}
for( ; idx < length && lowerCasePattern.charAt( idx ) == 'd'; idx++ )
{
buf.append('d');
}
if( endCode == ETimestamp.DAY_CODE )
break;
case ETimestamp.HOUR_CODE:
if( startCode != ETimestamp.HOUR_CODE )
{
buf.append(' ');
}
for( ; idx < length && lowerCasePattern.charAt( idx ) == 'h'; idx++ )
{
buf.append('H');
}
if( endCode == ETimestamp.HOUR_CODE )
break;
case ETimestamp.MINUTE_CODE:
if( startCode != ETimestamp.MINUTE_CODE )
{
buf.append(':');
}
for( ; idx < length && lowerCasePattern.charAt( idx ) == 'm'; idx++ )
{
buf.append('m');
}
if( endCode == ETimestamp.MINUTE_CODE )
break;
case ETimestamp.SECOND_CODE:
if( startCode != ETimestamp.SECOND_CODE )
{
buf.append(':');
}
for( ; idx < length && lowerCasePattern.charAt( idx ) == 's'; idx++ )
{
buf.append('s');
}
if( endCode == ETimestamp.SECOND_CODE )
break;
case ETimestamp.FRACTION1_CODE:
case ETimestamp.FRACTION2_CODE:
case ETimestamp.FRACTION3_CODE:
case ETimestamp.FRACTION4_CODE:
case ETimestamp.FRACTION5_CODE:
case ETimestamp.FRACTION6_CODE:
if( startCode <= ETimestamp.SECOND_CODE )
{
buf.append('.');
}
for( int i = 0; i < endCode - ETimestamp.SECOND_CODE; i++ )
{
buf.append('S');
}
}
return buf.toString();
}
/**
* Return length of the mask.
*/
public int getMaskLength()
{
return pattern.length();
}
/**
* Generate the number of y's in the mask.
*/
public int getYearDigits()
{
return getDigits('y');
}
/**
* Generate the number of M's in the mask.
*/
public int getMonthDigits()
{
return getDigits('m');
}
/**
* Generate the number of d's in the mask.
*/
public int getDayDigits()
{
return getDigits('d');
}
/**
* Generate the number of h's in the mask.
*/
public int getHourDigits()
{
return getDigits('h');
}
/**
* Generate the number of m's in the mask.
*/
public int getMinuteDigits()
{
return getDigits('m');
}
/**
* Generate the number of s's in the mask.
*/
public int getSecondDigits()
{
return getDigits('s');
}
/**
* Generate the number of f's in the mask.
*/
public int getFractionDigits()
{
return getDigits('f');
}
private int getDigits(char type)
{
int first = lowerCasePattern.indexOf(type);
if (first >= 0)
{
int last = lowerCasePattern.lastIndexOf(type);
return (last - first + 1);
}
else
{
return 0;
}
}
/**
* The maxValue is the maximum number of months that an interval(month) can store.
*/
public long getMaxMonthValue()
{
long maxValue = 0;
int firstY = lowerCasePattern.indexOf('y');
int firstM = lowerCasePattern.indexOf('m');
if (firstM >= 0)
{
// Pattern contains y's and M's
if (firstY >= 0)
{
maxValue = 11;
}
else
// Pattern only contains M's
{
int lastM = lowerCasePattern.lastIndexOf('m');
maxValue = maxValue(lastM - firstM + 1);
}
}
if (firstY >= 0)
{
int lastY = lowerCasePattern.lastIndexOf('y');
maxValue += maxValue(lastY - firstY + 1) * 12;
}
return maxValue;
}
/**
* The maxValue is the maximum number of seconds that an interval(second)
* can store before overflowing.
*/
public long getMaxSecondValue()
{
long maxValue = 0;
int multiplier;
char firstChar = lowerCasePattern.charAt(0);
switch (firstChar)
{
case 'd': multiplier = DateTimeUtil.SECONDS_PER_DAY;
break;
case 'h': multiplier = 3600;
break;
case 'm': multiplier = 60;
break;
case 's': multiplier = 1;
break;
default: multiplier = 0;
break;
}
maxValue = (maxValue(lowerCasePattern.lastIndexOf(firstChar) + 1) + 1)
* multiplier - 1;
return maxValue;
}
/**
* Calculate the maximum value that can be stored in a certain
* number of digits. Each digit has a value of 9.
* @param digits Number of digits
* @return Maximum value
*/
private long maxValue(int digits)
{
switch( digits )
{
case 1: return 9;
case 2: return 99;
case 3: return 999;
case 4: return 9999;
case 5: return 99999;
case 6: return 999999;
case 7: return 9999999;
case 8: return 99999999;
case 9: return 999999999;
default: return 0;
}
}
/**
* Deserializes an instance of this class.
*
* @param in The input stream.
* @throws IOException
* @throws ClassNotFoundException
*/
private void readObject( ObjectInputStream in )
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
lowerCasePattern = (pattern == null ? "" : pattern.toLowerCase());
}
}