/* This is wrapper class...
 Objective would be to push more functionality into this Class to enforce consistent definition
 */
public abstract class Generics {
	public final String masterType = "Generic";
	private String type;	// extender should define their data type

	// generic enumerated interface
	public interface KeyTypes {
		String name();
	}
	protected abstract KeyTypes getKey();  	// this method helps force usage of KeyTypes

	// getter
	public String getMasterType() {
		return masterType;
	}

	// getter
	public String getType() {
		return type;
	}

	// setter
	public void setType(String type) {
		this.type = type;
	}
	
	// this method is used to establish key order
	public abstract String toString();

	// static print method used by extended classes
	public static void print(Generics[] objs) {
		// print 'Object' properties
		System.out.println(objs.getClass() + " " + objs.length);

		// print 'Generics' properties
		if (objs.length > 0) {
			Generics obj = objs[0];	// Look at properties of 1st element
			System.out.println(
					obj.getMasterType() + ": " + 
					obj.getType() +
					" listed by " +
					obj.getKey());
		}

		// print "Generics: Objects'
		for(Object o : objs)	// observe that type is Opaque
			System.out.println(o);

		System.out.println();
	}
}
public class Level extends Generics {
    public static KeyTypes key = KeyType.title;
    public static void setOrder(KeyTypes key) { Level.key = key; }
    public enum KeyType implements KeyTypes {title, number, name, topic, difficulty, time, hint, hintContent}

    private final int number;
    private final String name;
    private final String topic;
    private final String difficulty;
    private final int time;
    private final boolean hint;
    private final String hintContent;

    public Level(int number, String name, String topic, String difficulty, int time, boolean hint, String hintContent) {
        super.setType("Level");
        this.number = number;
        this.name = name;
        this.topic = topic;
        this.difficulty = difficulty;
        this.time = time;
        this.hint = hint;
        this.hintContent = hintContent;
    }

    @Override
    protected KeyTypes getKey() { return Level.key; }

    @Override
    public String toString() {
        String output="";
        if (KeyType.number.equals(this.getKey())) {
            output += "00" + this.number;
            output = output.substring(output.length() - 2);
        }
        else if (KeyType.name.equals(this.getKey())) {
			output += this.name;
        }
        else if (KeyType.topic.equals(this.getKey())) {
			output += this.topic;
        }
        else if (KeyType.difficulty.equals(this.getKey())) {
			output += this.difficulty;
        }
        else if (KeyType.time.equals(this.getKey())) {
            output += "00" + this.time;
            output = output.substring(output.length() - 2);
        }
        else if (KeyType.hint.equals(this.getKey())) {
			output += this.hint;
        }
        else if (KeyType.hintContent.equals(this.getKey())) {
			output += this.hintContent;
        }
        else {
			output += super.getType() + ": " + this.number + ", " + this.topic + ", " + this.difficulty  + ", " + this.time  + ", " + this.hint  + ", " + this.hintContent;
		}
        return output;
        
    }

    public static Level[] levels() {
        return new Level[]{
                        new Level(1, "The Village","If Statements", "Easy", 30, true, "Try debugging"),
                        new Level(2,"The Jungle", "For Loops", "Medium", 60, false, "Take your time"),
                        new Level(3, "The Forest","De Morgan's Law", "Hard", 90, true, "Make a table for true and false"),
        };
    }

    public static void main(String[] args){
        Level[] objs = levels();

        Level.setOrder(KeyType.title);
        Level.print(objs);

        Level.setOrder(KeyType.name);
        Level.print(objs);

        Level.setOrder(KeyType.difficulty);
        Level.print(objs);
    }

}
Level.main(null);
class [LREPL.$JShell$13$Level; 3
Generic: Level listed by title
Level: 1, If Statements, Easy, 30, true, Try debugging
Level: 2, For Loops, Medium, 60, false, Take your time
Level: 3, De Morgan's Law, Hard, 90, true, Make a table for true and false

class [LREPL.$JShell$13$Level; 3
Generic: Level listed by name
The Village
The Jungle
The Forest

class [LREPL.$JShell$13$Level; 3
Generic: Level listed by difficulty
Easy
Medium
Hard

public class Cupcake extends Generics {
	// Class data
	public static KeyTypes key = KeyType.title;  // static initializer
	public static void setOrder(KeyTypes key) {Cupcake.key = key;}
	public enum KeyType implements KeyTypes {title, flavor, frosting, sprinkles}

	// Instance data
	private final String frosting;
	private final int sprinkles;
	private final String flavor;

	// Constructor
	Cupcake(String frosting, int sprinkles, String flavor)
	{
		this.setType("Cupcake");
		this.frosting = frosting;
		this.sprinkles = sprinkles;
		this.flavor = flavor;
	}

	/* 'Generics' requires getKey to help enforce KeyTypes usage */
	@Override
	protected KeyTypes getKey() { return Cupcake.key; }

	/* 'Generics' requires toString override
	 * toString provides data based off of Static Key setting
	 */
	@Override
	public String toString() {		
		String output="";
		if (KeyType.flavor.equals(this.getKey())) {
			output += this.flavor;
		} else if (KeyType.frosting.equals(this.getKey())) {
			output += this.frosting;
		} else if (KeyType.sprinkles.equals(this.getKey())) {
			output += "00" + this.sprinkles;
			output = output.substring(output.length() - 2);
		} else {
			output = super.getType() + ": " + this.flavor + ", " + this.frosting + ", " + this.sprinkles;
		}
		return output;
	}

	// Test data initializer
	public static Cupcake[] cupcakes() {
		return new Cupcake[]{
				new Cupcake("Red", 4, "Red Velvet"),
			    new Cupcake("Orange", 5, "Orange"),
			    new Cupcake("Yellow", 6, "Lemon"),
			    new Cupcake("Green", 7, "Apple"),
			    new Cupcake("Blue", 8, "Blueberry"),
			    new Cupcake("Purple", 9, "Blackberry"),
			    new Cupcake("Pink", 10, "Strawberry"),
			    new Cupcake("Tan", 11, "Vanilla"),
			    new Cupcake("Brown", 12, "Chocolate"),
		};
	}
	
	public static void main(String[] args)
	{
		// Inheritance Hierarchy
		Cupcake[] objs = cupcakes();

		// print with title
		Cupcake.setOrder(KeyType.title);
		Cupcake.print(objs);

		// print flavor only
		Cupcake.setOrder(KeyType.flavor);
		Cupcake.print(objs);
	}
	
}
Cupcake.main(null);
class [LREPL.$JShell$15$Cupcake; 9
Generic: Cupcake listed by title
Cupcake: Red Velvet, Red, 4
Cupcake: Orange, Orange, 5
Cupcake: Lemon, Yellow, 6
Cupcake: Apple, Green, 7
Cupcake: Blueberry, Blue, 8
Cupcake: Blackberry, Purple, 9
Cupcake: Strawberry, Pink, 10
Cupcake: Vanilla, Tan, 11
Cupcake: Chocolate, Brown, 12

class [LREPL.$JShell$15$Cupcake; 9
Generic: Cupcake listed by flavor
Red Velvet
Orange
Lemon
Apple
Blueberry
Blackberry
Strawberry
Vanilla
Chocolate

/*
 * Animal class extends Generics and defines abstract methods
 */
public class Animal extends Generics {
	// Class data
	public static KeyTypes key = KeyType.title;  // static initializer
	public static void setOrder(KeyTypes key) { Animal.key = key; }
	public enum KeyType implements KeyTypes {title, name, age, color}

	// Instance data
	private final String name;
	private final int age;
	private final String color;

	/* constructor
	 *
	 */
	public Animal(String name, int age, String color)
	{
		super.setType("Animal");
		this.name = name;
		this.age = age;
		this.color = color;
	}

	/* 'Generics' requires getKey to help enforce KeyTypes usage */
	@Override
	protected KeyTypes getKey() { return Animal.key; }
	
	/* 'Generics' requires toString override
	 * toString provides data based off of Static Key setting
	 */
	@Override
	public String toString()
	{
		String output="";
		if (KeyType.name.equals(this.getKey())) {
			output += this.name;
		} else if (KeyType.age.equals(this.getKey())) {
			output += "00" + this.age;
			output = output.substring(output.length() - 2);
		} else if (KeyType.color.equals(this.getKey())) {
			output += this.color;
		} else {
			output += super.getType() + ": " + this.name + ", " + this.color + ", " + this.age;
		}
		return output;
		
	}

	// Test data initializer
	public static Animal[] animals() {
		return new Animal[]{
				new Animal("Lion", 8, "Gold"),
				new Animal("Pig", 3, "Pink"),
				new Animal("Robin", 7, "Red"),
				new Animal("Cat", 10, "Black"),
				new Animal("Kitty", 1, "Calico"),
				new Animal("Dog", 14, "Brown")
		};
	}
	
	/* main to test Animal class
	 * 
	 */
	public static void main(String[] args)
	{
		// Inheritance Hierarchy
		Animal[] objs = animals();

		// print with title
		Animal.setOrder(KeyType.title);
		Animal.print(objs);

		// print name only
		Animal.setOrder(KeyType.name);
		Animal.print(objs);
	}

}
Animal.main(null);
class [LREPL.$JShell$17$Animal; 6
Generic: Animal listed by title
Animal: Lion, Gold, 8
Animal: Pig, Pink, 3
Animal: Robin, Red, 7
Animal: Cat, Black, 10
Animal: Kitty, Calico, 1
Animal: Dog, Brown, 14

class [LREPL.$JShell$17$Animal; 6
Generic: Animal listed by name
Lion
Pig
Robin
Cat
Kitty
Dog

public class Alphabet extends Generics {
	// Class data
	public static KeyTypes key = KeyType.title;  // static initializer
	public static void setOrder(KeyTypes key) {Alphabet.key = key;}
	public enum KeyType implements KeyTypes {title, letter}
	private static final int size = 26;  // constant used in data initialization

	// Instance data
	private final char letter;
	
	/*
	 * single letter object
	 */
	public Alphabet(char letter)
	{
		this.setType("Alphabet");
		this.letter = letter;
	}

	/* 'Generics' requires getKey to help enforce KeyTypes usage */
	@Override
	protected KeyTypes getKey() { return Alphabet.key; }

	/* 'Generics' requires toString override
	 * toString provides data based off of Static Key setting
	 */
	@Override
	public String toString()
	{
		String output="";
		if (KeyType.letter.equals(this.getKey())) {
			output += this.letter;
		} else {
			output += super.getType() + ": " + this.letter;
		}
		return output;
	}

	// Test data initializer for upper case Alphabet
	public static Alphabet[] alphabetData()
	{
		Alphabet[] alphabet = new Alphabet[Alphabet.size];
		for (int i = 0; i < Alphabet.size; i++)
		{
			alphabet[i] = new Alphabet( (char)('A' + i) );
		} 	
		return alphabet;
	}
	
	/* 
	 * main to test Animal class
	 */
	public static void main(String[] args)
	{
		// Inheritance Hierarchy
		Alphabet[] objs = alphabetData();

		// print with title
		Alphabet.setOrder(KeyType.title);
		Alphabet.print(objs);

		// print letter only
		Alphabet.setOrder(KeyType.letter);
		Alphabet.print(objs);
	}
	
}
Alphabet.main(null);
class [LREPL.$JShell$19$Alphabet; 26
Generic: Alphabet listed by title
Alphabet: A
Alphabet: B
Alphabet: C
Alphabet: D
Alphabet: E
Alphabet: F
Alphabet: G
Alphabet: H
Alphabet: I
Alphabet: J
Alphabet: K
Alphabet: L
Alphabet: M
Alphabet: N
Alphabet: O
Alphabet: P
Alphabet: Q
Alphabet: R
Alphabet: S
Alphabet: T
Alphabet: U
Alphabet: V
Alphabet: W
Alphabet: X
Alphabet: Y
Alphabet: Z

class [LREPL.$JShell$19$Alphabet; 26
Generic: Alphabet listed by letter
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z

/**
 *  Implementation of a Double Linked List;  forward and backward links point to adjacent Nodes.
 *
 */

 public class LinkedList<T>
 {
     private T data;
     private LinkedList<T> prevNode, nextNode;
 
     /**
      *  Constructs a new element
      *
      * @param  data, data of object
      * @param  node, previous node
      */
     public LinkedList(T data, LinkedList<T> node)
     {
         this.setData(data);
         this.setPrevNode(node);
         this.setNextNode(null);
     }
 
     /**
      *  Clone an object,
      *
      * @param  node  object to clone
      */
     public LinkedList(LinkedList<T> node)
     {
         this.setData(node.data);
         this.setPrevNode(node.prevNode);
         this.setNextNode(node.nextNode);
     }
 
     /**
      *  Setter for T data in DoubleLinkedNode object
      *
      * @param  data, update data of object
      */
     public void setData(T data)
     {
         this.data = data;
     }
 
     /**
      *  Returns T data for this element
      *
      * @return  data associated with object
      */
     public T getData()
     {
         return this.data;
     }
 
     /**
      *  Setter for prevNode in DoubleLinkedNode object
      *
      * @param node, prevNode to current Object
      */
     public void setPrevNode(LinkedList<T> node)
     {
         this.prevNode = node;
     }
 
     /**
      *  Setter for nextNode in DoubleLinkedNode object
      *
      * @param node, nextNode to current Object
      */
     public void setNextNode(LinkedList<T> node)
     {
         this.nextNode = node;
     }
 
 
     /**
      *  Returns reference to previous object in list
      *
      * @return  the previous object in the list
      */
     public LinkedList<T> getPrevious()
     {
         return this.prevNode;
     }
 
     /**
      *  Returns reference to next object in list
      *
      * @return  the next object in the list
      */
     public LinkedList<T> getNext()
     {
         return this.nextNode;
     }
 
 }
import java.util.Iterator;

/**
 * Queue Iterator
 *
 * 1. "has a" current reference in Queue
 * 2. supports iterable required methods for next that returns a generic T Object
 */
class QueueIterator<T> implements Iterator<T> {
    LinkedList<T> current;  // current element in iteration

    // QueueIterator is pointed to the head of the list for iteration
    public QueueIterator(LinkedList<T> head) {
        current = head;
    }

    // hasNext informs if next element exists
    public boolean hasNext() {
        return current != null;
    }

    // next returns data object and advances to next position in queue
    public T next() {
        T data = current.getData();
        current = current.getNext();
        return data;
    }


}

/**
 * Queue: custom implementation
 * @author     John Mortensen
 *
 * 1. Uses custom LinkedList of Generic type T
 * 2. Implements Iterable
 * 3. "has a" LinkedList for head and tail
 */
public class Queue<T> implements Iterable<T> {
    LinkedList<T> head = null, tail = null;
    protected int size;

    /**
     *  Add a new object at the end of the Queue,
     *
     * @param  data,  is the data to be inserted in the Queue.
     */
    public void add(T data) {
        // add new object to end of Queue
        LinkedList<T> tail = new LinkedList<>(data, null);

        if (this.head == null)  // initial condition
            this.head = this.tail = tail;
        else {  // nodes in queue
            this.tail.setNextNode(tail); // current tail points to new tail
            this.tail = tail;  // update tail
        }
    }

    /**
     *  Returns the data of head.
     *
     * @return  data, the dequeued data
     */
    public T delete() {
        T data = this.peek();
        if (this.tail != null) { // initial condition
            this.head = this.head.getNext(); // current tail points to new tail
            if (this.head != null) {
                this.head.setPrevNode(tail);
            }
        }
        return data;
    }

    public T dequeue() throws NoSuchElementException {
		
		if (isEmpty()) 
			throw new NoSuchElementException("empty queue");
		else {
			T e = this.head.getData();
			this.head = this.head.getNext();
			if (this.head == null) this.tail = null;
			this.size--;
			return e;
		}
	}

    public void enqueue(T data) {
		
		LinkedList<T> nn = new LinkedList<>(data, null);
		this.size++;
		if (this.head == null)
            this.head = nn;
		else
            this.tail.setNextNode(nn);
		this.tail = nn;
	}

    public int size() {
		
		return this.size;
	}

    public boolean isEmpty() {
		
		return this.head == null;
	}

    /**
     *  Returns the data of head.
     *
     * @return  this.head.getData(), the head data in Queue.
     */
    public T peek() {
        return this.head.getData();
    }

    /**
     *  Returns the head object.
     *
     * @return  this.head, the head object in Queue.
     */
    public LinkedList<T> getHead() {
        return this.head;
    }

    /**
     *  Returns the tail object.
     *
     * @return  this.tail, the last object in Queue
     */
    public LinkedList<T> getTail() {
        return this.tail;
    }

    /**
     *  Returns the iterator object.
     *
     * @return  this, instance of object
     */
    public Iterator<T> iterator() {
        return new QueueIterator<>(this.head);
    }
    
    
}
/**
 * Queue Manager
 * 1. "has a" Queue
 * 2. support management of Queue tasks (aka: titling, adding a list, printing)
 */
class QueueManager<T> {
    // queue data
    private final String name; // name of queue
    private int count = 0; // number of objects in queue
    public final Queue<T> queue = new Queue<>(); // queue object
    public final Queue<T> queue2 = new Queue<>(); // queue object
    public final Queue<Integer> newqueue = new Queue<>();

    /**
     *  Queue constructor
     *  Title with empty queue
     */
    public QueueManager(String name) {
        this.name = name;
    }

    /**
     *  Queue constructor
     *  Title with series of Arrays of Objects
     */
    public QueueManager(String name, T[]... seriesOfObjects) {
        this.name = name;
        this.addList(seriesOfObjects);
    }

    /**
     * Add a list of objects to queue
     */
    public void addList(T[]... seriesOfObjects) {  //accepts multiple generic T lists
        for (T[] objects: seriesOfObjects)
            for (T data : objects) {
                System.out.println(objects);
                System.out.println(data);
                this.queue.add(data);
                this.count++;
            }
            
    }


    /**
     * Print any array objects from queue
     */
    public void printQueue() {
        System.out.println(this.name + " count: " + count);
        System.out.print(this.name + " data: ");
        for (T data : queue)
            System.out.print(data + " ");
        System.out.println();
    }
}
/**
 * Queue Manager
 * 1. "has a" Queue
 * 2. support management of Queue tasks (aka: titling, adding a list, printing)
 */
class QueueMerger<T extends Comparable<T>> {
    // queue data
    private final String name; // name of queue
    private int count = 0; // number of objects in queue
    public final Queue<T> queue = new Queue<>(); // queue object
    public final Queue<T> queue2 = new Queue<>(); // queue object
    public final Queue<T> queue3 = new Queue<>();
    public final Queue<T> newqueue = new Queue<>();

    public final Stack<T> stack1 = new Stack<>();

    public final List<T> arr = new ArrayList<>();
    /**
     *  Queue constructor
     *  Title with empty queue
     */
    public QueueMerger(String name) {
        this.name = name;
    }

    /**
     *  Queue constructor
     *  Title with series of Arrays of Objects
     */
    public QueueMerger(String name, T[] list1, T[] list2) {
        this.name = name;
        this.addQueues(list1, list2);
        QueuetoStack(queue);
        shuffleQueue(queue3);
        deleteQueues(list1); 


    }

    /**
     * Add a list of objects to queue
     */
    public void addQueues(T[] list1, T[] list2) {  //accepts multiple generic T lists
        for (T data : list1) {
            this.queue.add(data);
            this.queue3.add(data);
        }
        for (T data : list2) {
            this.queue2.add(data);
        }   
            
    }

    public void deleteQueues(T[] list1) {  //accepts multiple generic T lists
        for (T data : list1) {
            this.queue3.delete();
        }
            
    }

    public void QueuetoStack(Queue<T> input1) {
        for (T x : input1) {
            this.stack1.push(x);
        }

    }

    public void shuffleQueue(Queue<T> input) {
        for (T x : input) {
            this.arr.add(x);
        }
        Collections.shuffle(arr);
            
    }

    public void addShuffle(Queue<T> inputt) {
        for (T y : arr) {
            inputt.enqueue(y);
        }
            
    }


    public void mergeHelper(Queue<T> input1, Queue<T> input2, Queue<T> output) {
		// TODO 3
		if (input1.isEmpty() && input2.isEmpty()) 
            return;
		if (input1.isEmpty()) 
			output.enqueue(input2.dequeue());
		else if (input2.isEmpty())
			output.enqueue(input1.dequeue());
		else {
            T e;
            if (input1.peek().compareTo(input2.peek()) <= 0) {
                e = input1.dequeue();
            }
            else {
                e = input2.dequeue();
            }
			output.enqueue(e);
		}
		mergeHelper(input1, input2, output);
	}

    /**
     * Print any array objects from queue
     */
    public void printQueue() {
        System.out.println("Inputted Queue 1");
        for (T data : queue)
            System.out.print(data + " ");
        
        System.out.println();
        System.out.println("Inputted Queue 2");
        for (T data : queue2)
            System.out.print(data + " ");
        mergeHelper(queue, queue2, newqueue);

        System.out.println();
        System.out.println("Merged and Ordered Queue: ");
        for (T data : newqueue)
            System.out.print(data + " ");

        System.out.println();

        System.out.println("Reverse Queue into Stack: ");
        System.out.print(stack1);
        System.out.println();

        System.out.println();
        System.out.print(arr);

        System.out.println();
        System.out.println("Shuffled Queue: ");
        
        addShuffle(queue3);
        for (T data : queue3)
            System.out.print(data + " ");

    }
}
/**
 * Driver Class
 * Tests queue with string, integers, and mixes of Classes and types
 */
class QueueTester {
    public static void main(String[] args)
    {


        // Create iterable Queue of Integers
        Comparable[] numbers = new Integer[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        Comparable[] numbers2 = new Integer[] { 2, 4, 6, 7, 7, 8, 10};
        QueueMerger qNums = new QueueMerger("Integers", numbers, numbers2);
        qNums.printQueue();
        

    }
}
QueueTester.main(null);
Inputted Queue 1
0 1 2 3 4 5 6 7 8 9 
Inputted Queue 2
2 4 6 7 7 8 10 
Merged and Ordered Queue: 
0 1 2 2 3 4 4 5 6 6 7 7 7 8 8 9 10 
Reverse Queue into Stack: 
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

[5, 9, 2, 1, 7, 0, 4, 8, 3, 6]
Shuffled Queue: 
5 9 2 1 7 0 4 8 3 6 
/**
 * Stack: custom implementation
 * @author     John Mortensen
 *
 * 1. Uses custom LinkedList of Generic type T
 * 2. "has a" LinkedList for Last In First Out (LIFO) object
 *
 */
public class Stack<T>
{
    private LinkedList<T> lifo = null;  // last in first out Object of stack

    /**
     *  Returns the current (LIFO) objects data.
     *
     * @return  the current data in Stack.
     */
    public T peek()
    {
        if (lifo == null)
            return null;
        else
            return lifo.getData();
    }

    /**
     *  Inserts a new data object at the top of this Stack,
     *
     * @param  data  to be inserted at the top of the Stack.
     */
    public void push(T data)
    {
        // note the order that things happen:
        // the new object becomes current and gets a value
        // current lifo is parameter, it is assigned as previous node in lifo
        lifo = new LinkedList<>(data, lifo);
    }

    /**
     *  Removes the top element in the Stack.
     *
     * @return  the popped data from the Stack.
     */
    public T pop()
    {
        T data = null;  // empty condition
        if (lifo != null) {
            data = lifo.getData();
            lifo = lifo.getPrevious();  // stack is overwritten with next item
        }
        return data;    // pop always returns data of element popped
    }

    /**
     *  Returns a string representation of this Stack,
     *  polymorphic nature of toString overrides of standard System.out.print behavior
     *
     * @return    string representation of data within Stack
     */
    public String toString()
    {
        StringBuilder stackToString = new StringBuilder("[");

        LinkedList<T> node = lifo;  				// start from the back
        while (node != null)
        {
            stackToString.append(node.getData()); 	// append the database to output string
            node = node.getPrevious();    		// go to previous node
            if (node != null)
                stackToString.append(", ");
        }										// loop 'till you reach the beginning
        stackToString.append("]");
        return stackToString.toString();
    }


}

/**
 * Stack Manager
 * 1. "has a" Stack
 * 2. support management of Stack tasks (aka: titling, adding a list, emptying, printing)
 *
 */
class StackDriver<T> {
    static public boolean DEBUG = false;
    private String title;
    private final Stack<T> stack = new Stack<>(); // stack object
    private int count;

    /**
     *  Stack constructor
     *
     * @param  title  name associated with stack
     * @param  seriesOfObjects  data to be inserted into stack
     */
    @SafeVarargs
    public StackDriver(String title, T[]... seriesOfObjects) {
        this.title = title;
        this.addList(seriesOfObjects);
    }

    /**
     *  Add a series of data object to the Stack
     *
     * @param  seriesOfObjects  data to be inserted into stack
     */
    @SafeVarargs
    public final void addList(T[]... seriesOfObjects)
    {
        if (DEBUG) System.out.println("Add " + title);
        for (T[] objects: seriesOfObjects)
            for (T data : objects) {
                this.stack.push(data);
                this.count++;
                if (DEBUG) System.out.println("Push: " + this.stack.peek() + " " + this.stack);
            }
        if (DEBUG) System.out.println();
    }

    /**
     *  Empty or remove all data objects from the Stack
     *
     */
    public void emptyStack()
    {
        if (DEBUG) System.out.println("Delete " + title);
        while (this.stack.peek() != null) {
            T data = this.stack.pop();
            if (DEBUG) System.out.println("Pop: " + data + " " + stack);
        }
        if (DEBUG) System.out.println();
    }

    /**
     *  Print analytics from the Stack
     *
     */
    public void printStack()
    {
        System.out.println("Size: " + count);
        System.out.println("Top Element: " + stack.peek());
        System.out.println("Full Stack: " + stack);
        System.out.println();
    }

}

class Main {
    /**
     * Test Stack functionality using different types of Objects
     *
     */
    public static void main(String[] args) {
        // Create Stack of Words
        StackDriver.DEBUG = false;
        String[] words = new String[]{"seven", "slimy", "snakes", "sallying", "slowly", "slithered", "southward"};
        StackDriver<String> sWords = new StackDriver<>("Words", words);
        sWords.printStack();
        sWords.emptyStack();

        // Create Stack of Integers
        StackDriver.DEBUG = false;
        Object[] numbers = new Integer[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
        StackDriver<Object> sNums = new StackDriver<>("Integers", numbers );
        sNums.printStack();
        sNums.emptyStack();

        StackDriver.DEBUG = false;
        Level.setOrder(Level.KeyType.name);
        StackDriver<Generics> Generics = new StackDriver<>("My Generics",
            Level.levels()
        );
        Generics.printStack();
        Generics.emptyStack();
    }
}

Main.main(null);
Size: 7
Top Element: southward
Full Stack: [southward, slithered, slowly, sallying, snakes, slimy, seven]

Size: 10
Top Element: 9
Full Stack: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Size: 3
Top Element: The Forest
Full Stack: [The Forest, The Jungle, The Village]