// Path: src/main/java/com/urbanairship/datacube/BucketType.java
// public class BucketType {
//     public static final BucketType IDENTITY = new BucketType("nobucket", new byte[]{});
//     public static final BucketType WILDCARD = new BucketType("wildcard", new byte[]{});
// 
//     private final String nameInErrMsgs;
//     private final byte[] uniqueId;
// 
//     public BucketType(String nameInErrMsgs, byte[] uniqueId) {
//         this.nameInErrMsgs = nameInErrMsgs;
//         this.uniqueId = uniqueId;
//     }
// 
//     public byte[] getUniqueId() {
//         return uniqueId;
//     }
// 
//     public String getNameInErrMsgs() {
//         return nameInErrMsgs;
//     }
// 
//     public String toString() {
//         return nameInErrMsgs;
//     }
// 
//     @Override
//     public boolean equals(Object o) {
//         if (this == o) return true;
//         if (!(o instanceof BucketType)) return false;
//         BucketType that = (BucketType) o;
//         return Objects.equals(nameInErrMsgs, that.nameInErrMsgs) &&
//                 Arrays.equals(uniqueId, that.uniqueId);
//     }
// 
//     @Override
//     public int hashCode() {
// 
//         int result = Objects.hash(nameInErrMsgs);
//         result = 31 * result + Arrays.hashCode(uniqueId);
//         return result;
//     }
// }
// 
// Path: src/main/java/com/urbanairship/datacube/Bucketer.java
// public interface Bucketer<F> {
//     /**
//      * When writing to the cube at some address, the address will have one coordinate for each
//      * dimension in the cube, for example (time: 348524388, location: portland). For each
//      * dimension, for each bucket type within that dimension, the bucketer must transform the
//      * input data into the bucket that should be used to store the data.
//      */
//     SetMultimap<BucketType, CSerializable> bucketForWrite(F coordinate);
// 
//     /**
//      * When reading from the cube, the reader specifies some coordinates from which to read.
//      * The bucketer can choose which cube coordinates to read from based on these input
//      * coordinates. For example, if the reader asks for hourly counts (the Hourly BucketType) and
//      * passes a timestamp, the bucketer could return the timestamp rounded down to the hour floor.
//      */
//     CSerializable bucketForRead(Object coordinate, BucketType bucketType);
// 
//     /**
//      * Return all bucket types that exist in this dimension.
//      */
//     List<BucketType> getBucketTypes();
// 
// 
//     /**
//      * This identity/no-op bucketer class is implicitly used for dimensions that don't choose a
//      * bucketer.
//      */
//     class IdentityBucketer implements Bucketer<CSerializable> {
//         private final List<BucketType> bucketTypes = ImmutableList.of(BucketType.IDENTITY);
// 
//         @Override
//         public SetMultimap<BucketType, CSerializable> bucketForWrite(CSerializable coordinate) {
//             return ImmutableSetMultimap.of(BucketType.IDENTITY, coordinate);
//         }
// 
//         @Override
//         public CSerializable bucketForRead(Object coordinateField, BucketType bucketType) {
//             return (CSerializable) coordinateField;
//         }
// 
//         @Override
//         public List<BucketType> getBucketTypes() {
//             return bucketTypes;
//         }
//     }
// }
// 
// Path: src/main/java/com/urbanairship/datacube/CSerializable.java
// public interface CSerializable {
//     byte[] serialize();
// }
// 
// Path: src/main/java/com/urbanairship/datacube/serializables/StringSerializable.java
// public class StringSerializable implements CSerializable {
//     private final String s;
// 
//     public StringSerializable(String s) {
//         this.s = s;
//     }
// 
//     @Override
//     public byte[] serialize() {
//         return staticSerialize(s);
//     }
// 
//     public static byte[] staticSerialize(String s) {
//         try {
//             return s.getBytes("UTF8");
//         } catch (UnsupportedEncodingException e) {
//             // No reasonable JVM will lack UTF8
//             throw new RuntimeException(e);
//         }
//     }
// }


import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.SetMultimap;
import com.urbanairship.datacube.BucketType;
import com.urbanairship.datacube.Bucketer;
import com.urbanairship.datacube.CSerializable;
import com.urbanairship.datacube.serializables.StringSerializable;
import java.util.Collection;
import java.util.List;
package com.urbanairship.datacube.bucketers;



/**
 * A Bucketer that accepts a list of strings and returns each one as a bucket. For example, this is useful when an
 * input type has an arbitrary number of tags associated with it (there would be a single "tags" dimension").
 */
public class TagsBucketer implements Bucketer<Collection<String>> {

    @Override
    public SetMultimap<BucketType, CSerializable> bucketForWrite(
            Collection<String> tags) {
        ImmutableSetMultimap.Builder<BucketType, CSerializable> builder =
                ImmutableSetMultimap.builder();
        for (String tag : tags) {
            builder.put(BucketType.IDENTITY, new StringSerializable(tag));
        }
        return builder.build();
    }

    @Override
    public CSerializable bucketForRead(Object coordinate, BucketType bucketType) {
// Path: src/main/java/com/urbanairship/datacube/BucketType.java
// public class BucketType {
//     public static final BucketType IDENTITY = new BucketType("nobucket", new byte[]{});
//     public static final BucketType WILDCARD = new BucketType("wildcard", new byte[]{});
// 
//     private final String nameInErrMsgs;
//     private final byte[] uniqueId;
// 
//     public BucketType(String nameInErrMsgs, byte[] uniqueId) {
//         this.nameInErrMsgs = nameInErrMsgs;
//         this.uniqueId = uniqueId;
//     }
// 
//     public byte[] getUniqueId() {
//         return uniqueId;
//     }
// 
//     public String getNameInErrMsgs() {
//         return nameInErrMsgs;
//     }
// 
//     public String toString() {
//         return nameInErrMsgs;
//     }
// 
//     @Override
//     public boolean equals(Object o) {
//         if (this == o) return true;
//         if (!(o instanceof BucketType)) return false;
//         BucketType that = (BucketType) o;
//         return Objects.equals(nameInErrMsgs, that.nameInErrMsgs) &&
//                 Arrays.equals(uniqueId, that.uniqueId);
//     }
// 
//     @Override
//     public int hashCode() {
// 
//         int result = Objects.hash(nameInErrMsgs);
//         result = 31 * result + Arrays.hashCode(uniqueId);
//         return result;
//     }
// }
// 
// Path: src/main/java/com/urbanairship/datacube/Bucketer.java
// public interface Bucketer<F> {
//     /**
//      * When writing to the cube at some address, the address will have one coordinate for each
//      * dimension in the cube, for example (time: 348524388, location: portland). For each
//      * dimension, for each bucket type within that dimension, the bucketer must transform the
//      * input data into the bucket that should be used to store the data.
//      */
//     SetMultimap<BucketType, CSerializable> bucketForWrite(F coordinate);
// 
//     /**
//      * When reading from the cube, the reader specifies some coordinates from which to read.
//      * The bucketer can choose which cube coordinates to read from based on these input
//      * coordinates. For example, if the reader asks for hourly counts (the Hourly BucketType) and
//      * passes a timestamp, the bucketer could return the timestamp rounded down to the hour floor.
//      */
//     CSerializable bucketForRead(Object coordinate, BucketType bucketType);
// 
//     /**
//      * Return all bucket types that exist in this dimension.
//      */
//     List<BucketType> getBucketTypes();
// 
// 
//     /**
//      * This identity/no-op bucketer class is implicitly used for dimensions that don't choose a
//      * bucketer.
//      */
//     class IdentityBucketer implements Bucketer<CSerializable> {
//         private final List<BucketType> bucketTypes = ImmutableList.of(BucketType.IDENTITY);
// 
//         @Override
//         public SetMultimap<BucketType, CSerializable> bucketForWrite(CSerializable coordinate) {
//             return ImmutableSetMultimap.of(BucketType.IDENTITY, coordinate);
//         }
// 
//         @Override
//         public CSerializable bucketForRead(Object coordinateField, BucketType bucketType) {
//             return (CSerializable) coordinateField;
//         }
// 
//         @Override
//         public List<BucketType> getBucketTypes() {
//             return bucketTypes;
//         }
//     }
// }
// 
// Path: src/main/java/com/urbanairship/datacube/CSerializable.java
// public interface CSerializable {
//     byte[] serialize();
// }
// 
// Path: src/main/java/com/urbanairship/datacube/serializables/StringSerializable.java
// public class StringSerializable implements CSerializable {
//     private final String s;
// 
//     public StringSerializable(String s) {
//         this.s = s;
//     }
// 
//     @Override
//     public byte[] serialize() {
//         return staticSerialize(s);
//     }
// 
//     public static byte[] staticSerialize(String s) {
//         try {
//             return s.getBytes("UTF8");
//         } catch (UnsupportedEncodingException e) {
//             // No reasonable JVM will lack UTF8
//             throw new RuntimeException(e);
//         }
//     }
// }


// Path: src/main/java/com/urbanairship/datacube/bucketers/TagsBucketer.java
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.SetMultimap;
import com.urbanairship.datacube.BucketType;
import com.urbanairship.datacube.Bucketer;
import com.urbanairship.datacube.CSerializable;
import com.urbanairship.datacube.serializables.StringSerializable;
import java.util.Collection;
import java.util.List;

package com.urbanairship.datacube.bucketers;



/**
 * A Bucketer that accepts a list of strings and returns each one as a bucket. For example, this is useful when an
 * input type has an arbitrary number of tags associated with it (there would be a single "tags" dimension").
 */
public class TagsBucketer implements Bucketer<Collection<String>> {

    @Override
    public SetMultimap<BucketType, CSerializable> bucketForWrite(
            Collection<String> tags) {
        ImmutableSetMultimap.Builder<BucketType, CSerializable> builder =
                ImmutableSetMultimap.builder();
        for (String tag : tags) {
            builder.put(BucketType.IDENTITY, new StringSerializable(tag));
        }
        return builder.build();
    }

    @Override
    public CSerializable bucketForRead(Object coordinate, BucketType bucketType) {
        return new StringSerializable((String) coordinate);