| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.cassandra.serializers; |
| |
| import java.nio.ByteBuffer; |
| import java.util.concurrent.TimeUnit; |
| import java.util.regex.Pattern; |
| |
| import org.apache.cassandra.utils.ByteBufferUtil; |
| |
| public class TimeSerializer implements TypeSerializer<Long> |
| { |
| public static final Pattern timePattern = Pattern.compile("^-?\\d+$"); |
| public static final TimeSerializer instance = new TimeSerializer(); |
| |
| public Long deserialize(ByteBuffer bytes) |
| { |
| return bytes.remaining() == 0 ? null : ByteBufferUtil.toLong(bytes); |
| } |
| |
| public ByteBuffer serialize(Long value) |
| { |
| return value == null ? ByteBufferUtil.EMPTY_BYTE_BUFFER : ByteBufferUtil.bytes(value); |
| } |
| |
| public static Long timeStringToLong(String source) throws MarshalException |
| { |
| // nano since start of day, raw |
| if (timePattern.matcher(source).matches()) |
| { |
| try |
| { |
| long result = Long.parseLong(source); |
| if (result < 0 || result >= TimeUnit.DAYS.toNanos(1)) |
| throw new NumberFormatException("Input long out of bounds: " + source); |
| return result; |
| } |
| catch (NumberFormatException e) |
| { |
| throw new MarshalException(String.format("Unable to make long (for time) from: '%s'", source), e); |
| } |
| } |
| |
| // Last chance, attempt to parse as time string |
| try |
| { |
| return parseTimeStrictly(source); |
| } |
| catch (IllegalArgumentException e1) |
| { |
| throw new MarshalException(String.format("(TimeType) Unable to coerce '%s' to a formatted time (long)", source), e1); |
| } |
| } |
| |
| public void validate(ByteBuffer bytes) throws MarshalException |
| { |
| if (bytes.remaining() != 8) |
| throw new MarshalException(String.format("Expected 8 byte long for time (%d)", bytes.remaining())); |
| } |
| |
| public String toString(Long value) |
| { |
| if (value == null) |
| return "null"; |
| |
| int nano = (int)(value % 1000); |
| value -= nano; |
| value /= 1000; |
| int micro = (int)(value % 1000); |
| value -= micro; |
| value /= 1000; |
| int milli = (int)(value % 1000); |
| value -= milli; |
| value /= 1000; |
| int seconds = (int)(value % 60); |
| value -= seconds; |
| value /= 60; |
| int minutes = (int)(value % 60); |
| value -= minutes; |
| value /= 60; |
| int hours = (int)(value % 24); |
| value -= hours; |
| value /= 24; |
| assert(value == 0); |
| |
| StringBuilder sb = new StringBuilder(); |
| leftPadZeros(hours, 2, sb); |
| sb.append(":"); |
| leftPadZeros(minutes, 2, sb); |
| sb.append(":"); |
| leftPadZeros(seconds, 2, sb); |
| sb.append("."); |
| leftPadZeros(milli, 3, sb); |
| leftPadZeros(micro, 3, sb); |
| leftPadZeros(nano, 3, sb); |
| return sb.toString(); |
| } |
| |
| private void leftPadZeros(int value, int digits, StringBuilder sb) |
| { |
| for (int i = 1; i < digits; ++i) |
| { |
| if (value < Math.pow(10, i)) |
| sb.append("0"); |
| } |
| sb.append(value); |
| } |
| |
| public Class<Long> getType() |
| { |
| return Long.class; |
| } |
| |
| // Time specific parsing loosely based on java.sql.Timestamp |
| private static Long parseTimeStrictly(String s) throws IllegalArgumentException |
| { |
| String nanos_s; |
| |
| long hour; |
| long minute; |
| long second; |
| long a_nanos = 0; |
| |
| String formatError = "Timestamp format must be hh:mm:ss[.fffffffff]"; |
| String zeros = "000000000"; |
| |
| if (s == null) |
| throw new java.lang.IllegalArgumentException(formatError); |
| s = s.trim(); |
| |
| // Parse the time |
| int firstColon = s.indexOf(':'); |
| int secondColon = s.indexOf(':', firstColon+1); |
| |
| // Convert the time; default missing nanos |
| if (firstColon > 0 && secondColon > 0 && secondColon < s.length() - 1) |
| { |
| int period = s.indexOf('.', secondColon+1); |
| hour = Integer.parseInt(s.substring(0, firstColon)); |
| if (hour < 0 || hour >= 24) |
| throw new IllegalArgumentException("Hour out of bounds."); |
| |
| minute = Integer.parseInt(s.substring(firstColon + 1, secondColon)); |
| if (minute < 0 || minute >= 60) |
| throw new IllegalArgumentException("Minute out of bounds."); |
| |
| if (period > 0 && period < s.length() - 1) |
| { |
| second = Integer.parseInt(s.substring(secondColon + 1, period)); |
| if (second < 0 || second >= 60) |
| throw new IllegalArgumentException("Second out of bounds."); |
| |
| nanos_s = s.substring(period + 1); |
| if (nanos_s.length() > 9) |
| throw new IllegalArgumentException(formatError); |
| if (!Character.isDigit(nanos_s.charAt(0))) |
| throw new IllegalArgumentException(formatError); |
| nanos_s = nanos_s + zeros.substring(0, 9 - nanos_s.length()); |
| a_nanos = Integer.parseInt(nanos_s); |
| } |
| else if (period > 0) |
| throw new IllegalArgumentException(formatError); |
| else |
| { |
| second = Integer.parseInt(s.substring(secondColon + 1)); |
| if (second < 0 || second >= 60) |
| throw new IllegalArgumentException("Second out of bounds."); |
| } |
| } |
| else |
| throw new IllegalArgumentException(formatError); |
| |
| long rawTime = 0; |
| rawTime += TimeUnit.HOURS.toNanos(hour); |
| rawTime += TimeUnit.MINUTES.toNanos(minute); |
| rawTime += TimeUnit.SECONDS.toNanos(second); |
| rawTime += a_nanos; |
| return rawTime; |
| } |
| } |