Skip to content

Commit

Permalink
AVRO-1497: Clean up Conversion and LogicalType classes.
Browse files Browse the repository at this point in the history
This moves the implementations into Conversions and LogicalTypes.
  • Loading branch information
rdblue committed Mar 29, 2015
1 parent ec8d6d4 commit e6e9761
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 315 deletions.
101 changes: 0 additions & 101 deletions lang/java/avro/src/main/java/org/apache/avro/Conversion.java
@@ -1,12 +1,8 @@
package org.apache.avro;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericEnumSymbol;
import org.apache.avro.generic.GenericFixed;
import org.apache.avro.generic.IndexedRecord;
Expand Down Expand Up @@ -139,101 +135,4 @@ public IndexedRecord toRecord(T value, Schema schema, LogicalType type) {
"toRecord is not supported for " + type.getName());
}

public static class UUIDConversion extends Conversion<UUID> {
@Override
public Class<UUID> getConvertedType() {
return UUID.class;
}

@Override
public Schema getRecommendedSchema() {
return LogicalType.uuid().addToSchema(Schema.create(Schema.Type.STRING));
}

@Override
public String getLogicalTypeName() {
return "uuid";
}

@Override
public UUID fromCharSequence(CharSequence value, Schema schema, LogicalType type) {
return UUID.fromString(value.toString());
}

@Override
public CharSequence toCharSequence(UUID value, Schema schema, LogicalType type) {
return value.toString();
}
}

public static class DecimalConversion extends Conversion<BigDecimal> {
@Override
public Class<BigDecimal> getConvertedType() {
return BigDecimal.class;
}

@Override
public Schema getRecommendedSchema() {
throw new UnsupportedOperationException(
"No recommended schema for decimal (scale is required)");
}

@Override
public String getLogicalTypeName() {
return "decimal";
}

@Override
public BigDecimal fromBytes(ByteBuffer value, Schema schema, LogicalType type) {
int scale = ((LogicalType.Decimal) type).getScale();
byte[] bytes;
if (value.hasArray()) {
bytes = value.array();
} else {
bytes = value.get(new byte[value.remaining()]).array();
}
return new BigDecimal(new BigInteger(bytes), scale);
}

@Override
public ByteBuffer toBytes(BigDecimal value, Schema schema, LogicalType type) {
int scale = ((LogicalType.Decimal) type).getScale();
if (scale != value.scale()) {
throw new AvroTypeException("Cannot encode decimal with scale " +
value.scale() + " as scale " + scale);
}
return ByteBuffer.wrap(value.unscaledValue().toByteArray());
}

@Override
public BigDecimal fromFixed(GenericFixed value, Schema schema, LogicalType type) {
int scale = ((LogicalType.Decimal) type).getScale();
return new BigDecimal(new BigInteger(value.bytes()), scale);
}

@Override
public GenericFixed toFixed(BigDecimal value, Schema schema, LogicalType type) {
int scale = ((LogicalType.Decimal) type).getScale();
if (scale != value.scale()) {
throw new AvroTypeException("Cannot encode decimal with scale " +
value.scale() + " as scale " + scale);
}

byte fillByte = (byte) (value.signum() < 0 ? 0xFF : 0x00);
byte[] unscaled = value.unscaledValue().toByteArray();
byte[] bytes = new byte[schema.getFixedSize()];
int offset = bytes.length - unscaled.length;

for (int i = 0; i < bytes.length; i += 1) {
if (i < offset) {
bytes[i] = fillByte;
} else {
bytes[i] = unscaled[i - offset];
}
}

return new GenericData.Fixed(schema, bytes);
}
}

}
109 changes: 109 additions & 0 deletions lang/java/avro/src/main/java/org/apache/avro/Conversions.java
@@ -0,0 +1,109 @@
package org.apache.avro;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.UUID;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericFixed;

public class Conversions {

public static class UUIDConversion extends Conversion<UUID> {
@Override
public Class<UUID> getConvertedType() {
return UUID.class;
}

@Override
public Schema getRecommendedSchema() {
return LogicalTypes.uuid().addToSchema(Schema.create(Schema.Type.STRING));
}

@Override
public String getLogicalTypeName() {
return "uuid";
}

@Override
public UUID fromCharSequence(CharSequence value, Schema schema, LogicalType type) {
return UUID.fromString(value.toString());
}

@Override
public CharSequence toCharSequence(UUID value, Schema schema, LogicalType type) {
return value.toString();
}
}

public static class DecimalConversion extends Conversion<BigDecimal> {
@Override
public Class<BigDecimal> getConvertedType() {
return BigDecimal.class;
}

@Override
public Schema getRecommendedSchema() {
throw new UnsupportedOperationException(
"No recommended schema for decimal (scale is required)");
}

@Override
public String getLogicalTypeName() {
return "decimal";
}

@Override
public BigDecimal fromBytes(ByteBuffer value, Schema schema, LogicalType type) {
int scale = ((LogicalTypes.Decimal) type).getScale();
byte[] bytes;
if (value.hasArray()) {
bytes = value.array();
} else {
bytes = value.get(new byte[value.remaining()]).array();
}
return new BigDecimal(new BigInteger(bytes), scale);
}

@Override
public ByteBuffer toBytes(BigDecimal value, Schema schema, LogicalType type) {
int scale = ((LogicalTypes.Decimal) type).getScale();
if (scale != value.scale()) {
throw new AvroTypeException("Cannot encode decimal with scale " +
value.scale() + " as scale " + scale);
}
return ByteBuffer.wrap(value.unscaledValue().toByteArray());
}

@Override
public BigDecimal fromFixed(GenericFixed value, Schema schema, LogicalType type) {
int scale = ((LogicalTypes.Decimal) type).getScale();
return new BigDecimal(new BigInteger(value.bytes()), scale);
}

@Override
public GenericFixed toFixed(BigDecimal value, Schema schema, LogicalType type) {
int scale = ((LogicalTypes.Decimal) type).getScale();
if (scale != value.scale()) {
throw new AvroTypeException("Cannot encode decimal with scale " +
value.scale() + " as scale " + scale);
}

byte fillByte = (byte) (value.signum() < 0 ? 0xFF : 0x00);
byte[] unscaled = value.unscaledValue().toByteArray();
byte[] bytes = new byte[schema.getFixedSize()];
int offset = bytes.length - unscaled.length;

for (int i = 0; i < bytes.length; i += 1) {
if (i < offset) {
bytes[i] = fillByte;
} else {
bytes[i] = unscaled[i - offset];
}
}

return new GenericData.Fixed(schema, bytes);
}
}

}
144 changes: 0 additions & 144 deletions lang/java/avro/src/main/java/org/apache/avro/LogicalType.java
Expand Up @@ -12,45 +12,8 @@ public class LogicalType {
SpecificData.KEY_CLASS_PROP, SpecificData.ELEMENT_PROP
};

public static LogicalType fromSchema(Schema schema) {
String typeName = schema.getProp(LOGICAL_TYPE_PROP);

LogicalType logicalType;
if ("decimal".equals(typeName)) {
logicalType = new Decimal(schema);
} else if ("uuid".equals(typeName)) {
logicalType = UUID_TYPE;
} else {
return null;
}

// make sure the type is valid before returning it
try {
logicalType.validate(schema);
} catch (RuntimeException e) {
// ignore invalid types
return null;
}

return logicalType;
}

/** Create a Decimal LogicalType with the given precision and scale 0 */
public static Decimal decimal(int precision) {
return decimal(precision, 0);
}

/** Create a Decimal LogicalType with the given precision and scale */
public static Decimal decimal(int precision, int scale) {
return new Decimal(precision, scale);
}

public static final LogicalType UUID_TYPE = new LogicalType("uuid");

public static LogicalType uuid() {
return UUID_TYPE;
}

private final String name;

protected LogicalType(String logicalTypeName) {
Expand All @@ -77,111 +40,4 @@ public void validate(Schema schema) {
}
}

/** Decimal represents arbitrary-precision fixed-scale decimal numbers */
public static class Decimal extends LogicalType {
private static final String PRECISION_PROP = "precision";
private static final String SCALE_PROP = "scale";

private final int precision;
private final int scale;

private Decimal(int precision, int scale) {
super("decimal");
this.precision = precision;
this.scale = scale;
}

private Decimal(Schema schema) {
super("decimal");
this.precision = getInt(schema, PRECISION_PROP);
this.scale = getInt(schema, SCALE_PROP);
}

@Override
public Schema addToSchema(Schema schema) {
super.addToSchema(schema);
schema.addProp(PRECISION_PROP, precision);
schema.addProp(SCALE_PROP, scale);
return schema;
}

public int getPrecision() {
return precision;
}

public int getScale() {
return scale;
}

@Override
public void validate(Schema schema) {
super.validate(schema);
// validate the type
if (schema.getType() != Schema.Type.FIXED &&
schema.getType() != Schema.Type.BYTES) {
throw new IllegalArgumentException(
"Logical type DECIMAL must be backed by fixed or bytes");
}
if (precision <= 0) {
throw new IllegalArgumentException("Invalid DECIMAL precision: " +
precision + " (must be positive)");
} else if (precision > maxPrecision(schema)) {
throw new IllegalArgumentException(
"fixed(" + schema.getFixedSize() + ") cannot store " +
precision + " digits (max " + maxPrecision(schema) + ")");
}
if (scale < 0) {
throw new IllegalArgumentException("Invalid DECIMAL scale: " +
scale + " (must be positive)");
} else if (scale > precision) {
throw new IllegalArgumentException("Invalid DECIMAL scale: " +
scale + " (greater than precision: " + precision + ")");
}
}

private long maxPrecision(Schema schema) {
if (schema.getType() == Schema.Type.BYTES) {
// not bounded
return Integer.MAX_VALUE;
} else if (schema.getType() == Schema.Type.FIXED) {
int size = schema.getFixedSize();
return Math.round( // convert double to long
Math.floor(Math.log10( // number of base-10 digits
Math.pow(2, 8 * size - 1) - 1) // max value stored
));
} else {
// not valid for any other type
return 0;
}
}

private int getInt(Schema schema, String name) {
Object obj = schema.getObjectProp(name);
if (obj instanceof Integer) {
return (Integer) obj;
}
throw new IllegalArgumentException("Expected int " + name + ": " +
(obj == null ? "null" : obj + ":" + obj.getClass().getSimpleName()));
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

Decimal decimal = (Decimal) o;

if (precision != decimal.precision) return false;
if (scale != decimal.scale) return false;

return true;
}

@Override
public int hashCode() {
int result = precision;
result = 31 * result + scale;
return result;
}
}
}

0 comments on commit e6e9761

Please sign in to comment.