Java tutorial
/** * The MIT License (MIT) * * Copyright (c) 2009-2015 FoundationDB, LLC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.foundationdb.sql.server; import com.foundationdb.server.error.InvalidParameterValueException; import com.foundationdb.server.error.UnsupportedCharsetException; import com.foundationdb.server.error.ZeroDateTimeException; import com.foundationdb.server.types.FormatOptions; import com.foundationdb.server.types.TExecutionContext; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.aksql.aktypes.AkGUID; import com.foundationdb.server.types.common.types.TString; import com.foundationdb.server.types.common.types.TypesTranslator; import com.foundationdb.server.types.mcompat.mtypes.MDateAndTime; import com.foundationdb.server.types.value.Value; import com.foundationdb.server.types.value.ValueSource; import com.foundationdb.server.types.value.ValueSources; import com.foundationdb.util.AkibanAppender; import com.foundationdb.util.Strings; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import java.math.BigDecimal; import java.sql.Types; import java.util.Collections; import java.util.Date; import java.io.*; /** Encode result values for transmission. */ public class ServerValueEncoder { public static enum ZeroDateTimeBehavior { NONE(null), EXCEPTION("exception"), ROUND("round"), CONVERT_TO_NULL("convertToNull"); private String propertyName; ZeroDateTimeBehavior(String propertyName) { this.propertyName = propertyName; } public static ZeroDateTimeBehavior fromProperty(String name) { if (name == null) return NONE; for (ZeroDateTimeBehavior zdtb : values()) { if (name.equals(zdtb.propertyName)) return zdtb; } throw new InvalidParameterValueException( String.format("Invalid name: %s for ZeroDateTimeBehavior", name)); } } public static final ValueSource ROUND_ZERO_DATETIME_SOURCE = new Value(MDateAndTime.DATETIME.instance(false), MDateAndTime.encodeDateTime(1, 1, 1, 0, 0, 0)); public static final ValueSource ROUND_ZERO_DATE_SOURCE = new Value(MDateAndTime.DATE.instance(false), MDateAndTime.encodeDate(1, 1, 1)); private final TypesTranslator typesTranslator; private final String encoding; private ZeroDateTimeBehavior zeroDateTimeBehavior; private FormatOptions options; private final ByteArrayOutputStream byteStream; private final PrintWriter printWriter; private final AkibanAppender appender; private DataOutputStream dataStream; public ServerValueEncoder(TypesTranslator typesTranslator, String encoding, FormatOptions options) { this(typesTranslator, encoding, new ByteArrayOutputStream(), options); } public ServerValueEncoder(TypesTranslator typesTranslator, String encoding, ZeroDateTimeBehavior zeroDateTimeBehavior, FormatOptions options) { this(typesTranslator, encoding, options); this.zeroDateTimeBehavior = zeroDateTimeBehavior; } public ServerValueEncoder(TypesTranslator typesTranslator, String encoding, ByteArrayOutputStream byteStream, FormatOptions options) { this.typesTranslator = typesTranslator; this.encoding = encoding; this.byteStream = byteStream; this.options = options; try { printWriter = new PrintWriter(new OutputStreamWriter(byteStream, encoding)); } catch (UnsupportedEncodingException ex) { throw new UnsupportedCharsetException(encoding); } // If the target encoding is UTF-8, we can support // canAppendBytes() for properly encoded source strings. if ("UTF-8".equals(encoding)) appender = AkibanAppender.of(byteStream, printWriter, "UTF-8"); else appender = AkibanAppender.of(printWriter); } public String getEncoding() { return encoding; } public ByteArrayOutputStream getByteStream() { printWriter.flush(); return byteStream; } public AkibanAppender getAppender() { return appender; } public DataOutputStream getDataStream() { printWriter.flush(); if (dataStream == null) dataStream = new DataOutputStream(byteStream); return dataStream; } /** * Encode the given value into a stream that can then be passed * to * <code>writeByteStream</code>. */ public ByteArrayOutputStream encodeValue(ValueSource value, ServerType type, boolean binary) throws IOException { if (value.isNull()) return null; if ((zeroDateTimeBehavior != ZeroDateTimeBehavior.NONE) && (((type.getType().typeClass() == MDateAndTime.DATE) && (value.getInt32() == 0)) || ((type.getType().typeClass() == MDateAndTime.DATETIME) && (value.getInt64() == 0)))) { switch (zeroDateTimeBehavior) { case EXCEPTION: throw new ZeroDateTimeException(); case ROUND: value = (type.getType().typeClass() == MDateAndTime.DATETIME) ? ROUND_ZERO_DATETIME_SOURCE : ROUND_ZERO_DATE_SOURCE; break; case CONVERT_TO_NULL: return null; } } reset(); appendValue(value, type, binary); return getByteStream(); } /** Encode the given direct value. */ public ByteArrayOutputStream encodePObject(Object value, ServerType type, boolean binary) throws IOException { if (value == null) return null; reset(); appendPObject(value, type, binary); return getByteStream(); } /** Reset the contents of the buffer. */ public void reset() { getByteStream().reset(); } /** Append the given value to the buffer. */ public void appendValue(ValueSource value, ServerType type, boolean binary) throws IOException { if (!binary) { // Handle unusual text encoding of binary types. switch (type.getBinaryEncoding()) { case BINARY_OCTAL_TEXT: processBinaryText(value); break; default: type.getType().format(value, appender); break; } } else { switch (type.getBinaryEncoding()) { case BINARY_OCTAL_TEXT: getByteStream().write(value.getBytes()); break; case INT_8: getDataStream().write((byte) typesTranslator.getIntegerValue(value)); break; case INT_16: getDataStream().writeShort((short) typesTranslator.getIntegerValue(value)); break; case INT_32: getDataStream().writeInt((int) typesTranslator.getIntegerValue(value)); break; case INT_64: getDataStream().writeLong(typesTranslator.getIntegerValue(value)); break; case FLOAT_32: getDataStream().writeFloat(value.getFloat()); break; case FLOAT_64: getDataStream().writeDouble(value.getDouble()); break; case STRING_BYTES: getByteStream().write(value.getString().getBytes(encoding)); break; case BOOLEAN_C: getDataStream().write(value.getBoolean() ? 1 : 0); break; case TIMESTAMP_FLOAT64_SECS_2000_NOTZ: getDataStream().writeDouble(seconds2000NoTZ(typesTranslator.getTimestampMillisValue(value)) + typesTranslator.getTimestampNanosValue(value) / 1.0e9); break; case TIMESTAMP_INT64_MICROS_2000_NOTZ: getDataStream().writeLong(seconds2000NoTZ(typesTranslator.getTimestampMillisValue(value)) * 1000000L + typesTranslator.getTimestampNanosValue(value) / 1000); break; case DAYS_2000: getDataStream().writeInt(days2000(typesTranslator.getTimestampMillisValue(value))); break; case TIME_FLOAT64_SECS_NOTZ: getDataStream().writeDouble(timeSecsNoTZ(typesTranslator.getTimestampMillisValue(value))); break; case TIME_INT64_MICROS_NOTZ: getDataStream().writeLong(timeSecsNoTZ(typesTranslator.getTimestampMillisValue(value)) * 1000000L); break; case DECIMAL_PG_NUMERIC_VAR: for (short d : pgNumericVar(typesTranslator.getDecimalValue(value))) { getDataStream().writeShort(d); } break; case UUID: getDataStream().write(AkGUID.uuidToBytes((java.util.UUID) value.getObject())); break; case NONE: default: throw new UnsupportedOperationException("No binary encoding for " + type); } } } private void processBinaryText(ValueSource value) { FormatOptions.BinaryFormatOption bfo = options.get(FormatOptions.BinaryFormatOption.class); String formattedString = bfo.format(value.getBytes()); printWriter.append(formattedString); } /** Append the given direct object to the buffer. */ public void appendPObject(Object value, ServerType type, boolean binary) throws IOException { if (type.getType().typeClass() instanceof TString && value instanceof String) { // Optimize the common case of directly encoding a string. printWriter.write((String) value); return; } ValueSource source = valuefromObject(value, type); appendValue(source, type, binary); } public ValueSource valuefromObject(Object value, ServerType type) { if (value instanceof Date) { TInstance dateType = javaDateTInstance(value); Value dateValue = new Value(dateType); typesTranslator.setTimestampMillisValue(dateValue, ((Date) value).getTime(), (value instanceof java.sql.Timestamp) ? ((java.sql.Timestamp) value).getNanos() : 0); TInstance targetType = type.getType(); if (dateType.equals(targetType)) return dateValue; TExecutionContext context = new TExecutionContext(Collections.singletonList(dateType), targetType, null); Value result = new Value(targetType); targetType.typeClass().fromObject(context, dateValue, result); return result; } else { // TODO this is inefficient, but I want to get it working. return ValueSources.valuefromObject(value, type.getType()); } } private TInstance javaDateTInstance(Object value) { int jdbcType; if (value instanceof java.sql.Date) { jdbcType = Types.DATE; } else if (value instanceof java.sql.Time) { jdbcType = Types.TIME; } else { jdbcType = Types.TIMESTAMP; } return typesTranslator.typeClassForJDBCType(jdbcType).instance(true); } public void appendString(String string) throws IOException { printWriter.write(string); } public PrintWriter getWriter() { return printWriter; } /** Adjust milliseconds since 1970-01-01 00:00:00-UTC to seconds since * 2000-01-01 00:00:00 timezoneless. A conversion from local time * to UTC involves an offset that varies for Summer time. A * conversion from local time to timezoneless just removes the * zone as though all days were the same length. */ private static long seconds2000NoTZ(long millis) { DateTimeZone dtz = DateTimeZone.getDefault(); millis += dtz.getOffset(millis); return millis / 1000 - 946684800; // 2000-01-01 00:00:00-UTC. } public static int days2000(long millis) { long secs = seconds2000NoTZ(millis); return (int) (secs / 86400); } public static int timeSecsNoTZ(long millis) { DateTime dt = new DateTime(millis); return dt.getSecondOfDay(); } private static final short NUMERIC_POS = 0x0000; private static final short NUMERIC_NEG = 0x4000; private static final short NUMERIC_NAN = (short) 0xC000; private static short[] pgNumericVar(BigDecimal n) { short ndigits, weight, sign, dscale; dscale = (short) n.scale(); if (dscale < 0) dscale = 0; String s = n.toPlainString(); int lpos = 0; sign = NUMERIC_POS; if (s.charAt(lpos) == '-') { sign = NUMERIC_NEG; lpos++; } int dposl = s.indexOf('.', lpos), dposr; if (dposl < 0) dposr = dposl = s.length(); else dposr = dposl + 1; int nleft = (dposl - lpos + 3) / 4; weight = (short) (nleft - 1); int nright = (s.length() - dposr + 3) / 4; ndigits = (short) (nleft + nright); while ((ndigits > 0) && (pgNumericDigit(s, ndigits - 1, lpos, dposl, dposr, nleft, nright) == 0)) { ndigits--; } short[] digits = new short[ndigits + 4]; digits[0] = ndigits; digits[1] = weight; digits[2] = sign; digits[3] = dscale; for (int i = 0; i < ndigits; i++) { digits[i + 4] = pgNumericDigit(s, i, lpos, dposl, dposr, nleft, nright); } return digits; } private static short pgNumericDigit(String s, int index, int lpos, int dposl, int dposr, int nleft, int nright) { short result = 0; if (index < nleft) { int pos = dposl + (index - nleft) * 4; for (int i = 0; i < 4; i++) { result = (short) (result * 10); if (pos >= lpos) result += s.charAt(pos) - '0'; pos++; } } else { int pos = dposr + (index - nleft) * 4; for (int i = 0; i < 4; i++) { result = (short) (result * 10); if (pos < s.length()) result += s.charAt(pos) - '0'; pos++; } } return result; } }