/**
 * Copyright 2013 Cloudera 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.cloudera.cdk.data;

import com.cloudera.cdk.data.spi.Conversions;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * <p>
 * A key for retrieving entities from a {@link RandomAccessDataset}.
 * </p>
 *
 * @since 0.9.0
 */
public class Key {

  private final List<Object> values;

  Key(List<Object> values) {
    this.values = values;
  }

  /**
   * Returns the value for {@code index}.
   *
   * @param index the {@code index} of the value to return
   * @return the Object stored at {@code index}
   */
  public Object get(int index) {
    return values.get(index);
  }

  @Override
  public int hashCode() {
    return Objects.hashCode(values);
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof Key) {
      return Objects.equal(values, ((Key) obj).values);
    } else {
      return false;
    }
  }

  @Override
  public String toString() {
    return Objects.toStringHelper(this).add("values", values).toString();
  }

  /**
   * A fluent builder to aid in the construction of {@link Key} objects.
   *
   * @since 0.9.0
   */
  public static class Builder {

    private PartitionStrategy strategy;
    private Set<String> fieldNames;
    private final Map<String, Object> values;

    /**
     * Construct a {@link Builder} for a {@link RandomAccessDataset}.
     */
    public Builder(RandomAccessDataset dataset) {
      this.strategy = dataset.getDescriptor().getPartitionStrategy();
      this.fieldNames = Sets.newHashSet();
      for (FieldPartitioner fp : strategy.getFieldPartitioners()) {
        fieldNames.add(fp.getSourceName());
        fieldNames.add(fp.getName());
      }
      this.values = Maps.newHashMap();
    }

    /**
     * Add a key value for the named field.
     *
     * @throws IllegalArgumentException If the there is no key field named
     * <code>name</code> for this builder's dataset.
     * @return An instance of the builder for method chaining.
     */
    public Builder add(String name, Object value) {
      Preconditions.checkArgument(fieldNames.contains(name), "Field %s not in schema.",
          name);
      values.put(name, value);
      return this;
    }

    /**
     * Build an instance of the configured key.
     */
    @SuppressWarnings("unchecked")
    public Key build() {
      final List<FieldPartitioner> partitioners = strategy.getFieldPartitioners();
      final List<Object> content = Lists.newArrayListWithCapacity(partitioners.size());
      for (FieldPartitioner fp : partitioners) {
        content.add(valueFor(fp));
      }
      return new Key(content);
    }

    private <S, T> T valueFor(FieldPartitioner<S, T> fp) {
      if (values.containsKey(fp.getName())) {
        return Conversions.convert(values.get(fp.getName()), fp.getType());

      } else if (values.containsKey(fp.getSourceName())) {
        return fp.apply(Conversions.convert(
            values.get(fp.getSourceName()),
            fp.getSourceType()));

      } else {
        throw new IllegalStateException(
            "Cannot create Key, missing data for field:" + fp.getName());
      }
    }
  }
}
@@ -3023,16 +3023,98 @@
 ed key.%0A
+     *%0A     * @throws IllegalStateException If any required key field is missing.%0A
      */%0A
@@ -3988,8 +3988,9 @@
  %7D%0A  %7D%0A%7D
+%0A

Add @throws to Key.Builder#build.