//: cY:Fill2.java
// Using adapters to compensate for lack of latent typing.
import java.util.*;
import java.lang.reflect.*;

interface Generator<T> { T next(); }

interface Addable<T> { void add(T t); }

public class Fill2 {
  // Classtoken version:
  public static <T> void fill(Addable<T> addable,
    Class<? extends T> classToken, int size) {
    for(int i = 0; i < size; i++)
      try {
        addable.add(classToken.newInstance());
      } catch(Exception e) {
        throw new RuntimeException(e);
      }
  }
  // Generator version:
  public static <T> void
  fill(Addable<T> addable, Generator<T> generator, int size) {
    for(int i = 0; i < size; i++)
      addable.add(generator.next());
  }
}

// If you're adapting a base type, you must use composition.
// Make any Collection Addable using composition:
class AddableCollectionAdapter<T> implements Addable<T> {
  private Collection<T> c;
  public AddableCollectionAdapter(Collection<T> c) {
    this.c = c;
  }
  public void add(T item) { c.add(item); }
}

// Helper method for above, captures the type automatically:
class Adapter {
  public static <T>
  Addable<T> collectionAdapter(Collection<T> c) {
    return new AddableCollectionAdapter<T>(c);
  }
}

// If you're adapting a specific type, you can use inheritance:
// Make a SimpleQueue Addable using inheritance:
class AddableSimpleQueue<T>
extends SimpleQueue<T> implements Addable<T> {
  public void add(T item) { super.add(item); }
}

// Another type of container created by someone else:
class RandomList<T> {
  private ArrayList<T> storage = new ArrayList<T>();
  private Random rand = new Random();
  public void insert(T item) { storage.add(item); }
  public T select() {
    return storage.get(rand.nextInt(storage.size()));
  }
}

// Abstract composition adapter:
abstract class AddableAdapter<S, T> implements Addable<T> {
  protected S seq;
  public AddableAdapter(S sequence) { seq = sequence; }
  public abstract void add(T item);
}

class Coffee {}
class Latte extends Coffee {}
class Mocha extends Coffee {}
class Cappucino extends Coffee {}
class Americano extends Coffee {}
class Breve extends Coffee {}

// Generate different types of Coffee:
class CoffeeGenerator implements Generator<Coffee> {
  private Class[] types = { Latte.class, Mocha.class,
    Cappucino.class, Americano.class, Breve.class, };
  private Random rand = new Random();
  public Coffee next() {
    try {
      return (Coffee)types[rand.nextInt(types.length)].newInstance();
      // Report programmer errors at runtime:
    } catch(Exception e) {
      throw new RuntimeException(e);
    }
  }
}

class Fill2Test {
  public static void main(String[] args) {
    // Adapt a Collection:
    List<Coffee> coffeeCarrier = new ArrayList<Coffee>();
    Fill2.fill(new AddableCollectionAdapter<Coffee>(coffeeCarrier),
      Coffee.class, 3);
    // Helper method captures the type:
    Fill2.fill(Adapter.collectionAdapter(coffeeCarrier),
      Latte.class, 2);
    for(Coffee c: coffeeCarrier)
      System.out.println(c);

    System.out.println("----------------------");

    // Use an adapted class:
    AddableSimpleQueue<Coffee> coffeeQueue =
      new AddableSimpleQueue<Coffee>();
    Fill2.fill(coffeeQueue, Mocha.class, 4);
    Fill2.fill(coffeeQueue, Latte.class, 1);
    for(Coffee c: coffeeQueue)
      System.out.println(c);

    System.out.println("++++++++++++++++++++++");

    RandomList<Coffee> rl = new RandomList<Coffee>();
    // An on-the-fly adapter via an anonymous class:
    Addable<Coffee> ac =
      new AddableAdapter<RandomList<Coffee>, Coffee>(rl) {
        public void add(Coffee item) { seq.insert(item); }
      };
    Fill2.fill(ac, new CoffeeGenerator(), 7);
    for(int i = 0; i < 10; i++)
      System.out.println(rl.select());
  }
}

