/*
* Copyright (C) 2012 Facebook, Inc.
*
* Licensed 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 com.facebook.data.types;
import com.facebook.collectionsbase.Lists;
import com.facebook.util.serialization.SerDe;
import com.facebook.util.serialization.SerDeException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
/**
* represents a list of Datums. Retrieve the list via asRaw();
* <p/>
* of the list, but this might lead to confusing issues. Ways to get the List\<Datum\>
* <pre>
* // preferred, though more verbose
* void fuu(Datum someDatum) {
* if (someDatum.getType() == DatumType.LIST) {
* ListDatum listDatum = (ListDatum)someDatum;
* List<Datum> datumList = listDatum.asList();
* }
* }
*
* // more concise, but asRaw() thus far has a loose contract and is only
* void bar(Datum someDatum) {
* if (someDatum.getType() == DatumType.LIST) {
* List<Datum> datumList = (List<Datum>)someDatum.asRaw();
* }
* }
*
*
* </pre>
*
*/
public class ListDatum implements Datum {
private static final int STRING_DATUM_SIZE_ESTIMATE = 8;
private static final char DEFAULT_SEPARATOR = '\001';
private final List<Datum> datumList;
private final char separator;
private final Datum scalarDatum;
private volatile String cachedStringDatum = null;
private volatile byte[] cachedBytes = null;
public ListDatum(
List<Datum> datumList, char separator
) {
this.datumList = datumList;
this.separator = separator;
if (datumList.size() == 1) {
scalarDatum = datumList.get(0);
} else {
scalarDatum = null;
}
}
public ListDatum(List<Datum> datumList) {
this(datumList, DEFAULT_SEPARATOR);
}
char getSeparator() {
return separator;
}
List<Datum> getDatumList() {
return datumList;
}
@Override
public boolean asBoolean() {
return !datumList.isEmpty();
}
@Override
public byte asByte() {
if (scalarDatum != null) {
return scalarDatum.asByte();
} else {
throw new UnsupportedOperationException();
}
}
@Override
public short asShort() {
if (scalarDatum != null) {
return scalarDatum.asShort();
} else {
throw new UnsupportedOperationException();
}
}
@Override
public int asInteger() {
if (datumList.size() == 1) {
return scalarDatum.asInteger();
} else {
throw new UnsupportedOperationException();
}
}
@Override
public long asLong() {
if (scalarDatum != null) {
return scalarDatum.asLong();
} else {
throw new UnsupportedOperationException();
}
}
@Override
public float asFloat() {
if (scalarDatum != null) {
return scalarDatum.asFloat();
} else {
throw new UnsupportedOperationException();
}
}
@Override
public double asDouble() {
if (scalarDatum != null) {
return scalarDatum.asDouble();
} else {
throw new UnsupportedOperationException();
}
}
@Override
public byte[] asBytes() {
if (cachedBytes == null) {
try {
cachedBytes = asString().getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
// this shouldn't happen, but it's fatal of it does
throw new RuntimeException(e);
}
}
return cachedBytes;
}
@Override
public String asString() {
if (scalarDatum != null) {
cachedStringDatum = scalarDatum.asString();
} else if (cachedStringDatum == null) {
StringBuilder sb = new StringBuilder(
datumList.size() * STRING_DATUM_SIZE_ESTIMATE
);
for (Datum datum : datumList) {
if (sb.length() > 0) {
sb.append(separator);
}
sb.append(datum.asString());
}
cachedStringDatum = sb.toString();
}
return cachedStringDatum;
}
@Override
public boolean isNull() {
// TODO: is [NulLDatum] == null ? i say no as does this impl
return false;
}
@Override
public DatumType getType() {
return DatumType.LIST;
}
@Override
public Object asRaw() {
return datumList;
}
/**
* same as asRaw(), but just saves you from having to do
* <pre> {@code
* List<Datum> list = (List<Datum>)((ListDatum)datum).asRaw()
* } </pre>
* becomes
* <pre> {@code
* List<Datum> list = ((ListDatum).datum).asList()
* } </pre>
*
* @return
*/
public List<Datum> asList() {
return datumList;
}
@Override
public String toString() {
return asString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ListDatum)) {
return false;
}
final ListDatum listDatum = (ListDatum) o;
if (datumList != null ? !datumList.equals(listDatum.datumList) : listDatum.datumList != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = datumList != null ? datumList.hashCode() : 0;
result = 31 * result + (scalarDatum != null ? scalarDatum.hashCode() : 0);
return result;
}
@Override
public int compareTo(Datum o) {
if (o instanceof ListDatum) {
ListDatum otherListDatum = (ListDatum) o;
return Lists.compareLists(datumList, otherListDatum.datumList);
} else {
throw new IllegalArgumentException(
String.format(
"ListDatum cannot compare to %s", o.getClass()
)
);
}
}
public static class SerDeImpl implements SerDe<Datum> {
@Override
public Datum deserialize(DataInput in) throws SerDeException {
try {
int numItems = in.readInt();
List<Datum> datumList = new ArrayList<Datum>(numItems);
for (int i = 0; i < numItems; i++) {
byte typeAsByte = in.readByte();
DatumType datumType = DatumType.fromByte(typeAsByte);
Datum datum = datumType.getSerDe().deserialize(in);
datumList.add(datum);
}
ListDatum listDatum = new ListDatum(datumList);
return listDatum;
} catch (IOException e) {
throw new SerDeException(e);
}
}
@Override
public void serialize(Datum value, DataOutput out)
throws SerDeException {
if (value instanceof ListDatum) {
try {
ListDatum listDatum = (ListDatum) value;
out.writeInt(listDatum.datumList.size());
for (Datum datum : listDatum.datumList) {
DatumType datumType = datum.getType();
SerDe<Datum> datumSerDe = datumType.getSerDe();
// write type
out.writeByte(datumType.getTypeAsByte());
// now the datm
datumSerDe.serialize(datum, out);
}
} catch (IOException e) {
throw new SerDeException(e);
}
} else {
throw new IllegalArgumentException(
"ListDatum.SerDeImpl requires ListDatum, not " +
value.getClass()
);
}
}
}
}