/*
 * $Id$
 *
 * Copyright (c) 2002, 2009, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.sun.demoapi.lists;

/**
 * Simple linked lists of objects.
 * A list is represented by a series of Entry objects, each containing
 * an item of client data, and a link to the next entry in the list.
 * The list keeps references to the first and last entry in the list.
 */

// Note: this class is purely provided to be the basis of some
// examples for writing a testsuite. The code has been written with
// simplicity in mind, rather than efficiency, and may contain
// deliberate coding errors. For proper support for linked lists,
// see the classes in java.util.

public class DoublyLinkedList
{
    /**
     * An entry in a DoublyLinkedList, containing client data
     * and links to the previous and next entries.
     */
    public class Entry {
        /**
         * Create an entry to be put in a DoublyLinkedList.
         * Entries are not created directly by the client:
         * they are created automatically by the methods that
         * insert data into the list as a whole.
         * @param data Client data to be stored in this entry
         * @param next The next entry to appear after this one.
         * @see #insert
         * @see ##append
         * @see #insertAfter
         */
        Entry(Object data, Entry prev, Entry next) {
            this.data = data;
            this.prev = prev;
            this.next = next;
        }

        /**
         * Get the client data in this entry in the list
         * @return the client data in this entry in the list
         */
        public Object getData() {
            return data;
        }

        /**
         * Get the next entry in the list, if any.
         * @return the next entry in the list,
         * or null if this is the last entry.
         */
        public Entry getNext() {
            return next;
        }


        /**
         * Get the previous entry in the list, if any.
         * @return the previous entry in the list,
         * or null if this is the first entry.
         */
        public Entry getPrevious() {
            return prev;
        }

        /**
         * Insert a new entry in the list, after this one.
         * @param data the client data to be stored in this entry
         */
        public void insertAfter(Object data) {
            Entry e = new Entry(data, this, next);
            if (next == null)
                last = e;
            else
                next.prev = e;
            next = e;
        }


        /**
         * Insert a new entry in the list, after this one.
         * @param data the client data to be stored in this entry
         */
        public void insertBefore(Object data) {
            Entry e = new Entry(data, prev, this);
            if (prev == null)
                first = e;
            else
                prev.next = e;
            prev = e;
        }

        /**
         * Remove this entry from the list.
         * @return the next entry in the list, or null if none
         */
        public Entry remove() {
            if (prev == null)
                first = next;
            else
                prev.next = next;

            if (next == null)
                last = prev;
            else
                next.prev = prev;

            return next;
        }

        Object data;
        Entry prev;
        Entry next;
    }

    /**
     * Create an empty linked list.
     */
    public DoublyLinkedList() {
    }

    /**
     * Determine if a linked list is empty.
     * @return true if the list has no entries, and false otherwise.
     */
    public boolean isEmpty() {
        return (first == null);
    }

    /**
     * Determine if the list contains an entry with a specific item of
     * client data.
     * @return true if the list contains an entry that matches the
     * given client data, and false otherwise.
     */
    public boolean contains(Object data) {
        for (Entry e = first; e != null; e = e.next) {
            if (e.data == data)
                return true;
        }
        return false;
    }

    /**
     * Get the first entry in the list.
     * @return the first entry in the list
     * @see Entry#getNext
     */
    public Entry getFirst() {
        return first;
    }

    /**
     * Get the last entry in the list.
     * @return the first entry in the list
     * @see Entry#getNext
     */
    public Entry getLast() {
        return last;
    }

    /**
     * Insert a new entry containing the specified client data
     * at the beginning of the list.
     * @param data the client data for the new entry
     */
    public void insert(Object data) {
        Entry e = new Entry(data, null, first);
        if (first == null)
            last = e;
        else
            first.prev = e;
        first = e;
    }

    /**
     * Append an entry containing the specified client data
     * to the end of the list.
     */
    public void append(Object o) {
        Entry e = new Entry(o, last, null);
        if (first == null)
            first = e;
        else
            last.next = e;
        last = e;
    }

    /**
     * Remove the first entry from the list that contains the
     * specified client data.
     * @param data The client data indicating the entry to be removed
     * @return true if an entry was found and removed that contained
     * the specified client data, and false otherwise.
     */
    public boolean remove(Object data) {
        for (Entry e = first, prev = null; e != null; prev = e, e = e.next) {
            if (e.data == data) {
                e.remove();
                return true;
            }
        }

        // not found
        return false;
    }

    /**
     * Check if the contents of this list match another.
     * @param other An object to be checked for equality with this one
     * @return true if the other object is a linked list, and corresponding
     * entries in the two lists are either both null, or are equal.
     */
    public boolean equals(Object other) {
        if (!(other instanceof DoublyLinkedList))
            return false;

        Entry e1 = first;
        Entry e2 = ((DoublyLinkedList) other).first;
        while (e1 != null && e2 != null) {
            boolean match = (e1.data == null ? e2.data == null : e1.data.equals(e2.data));
            if (!match)
                return false;

             e1 = e1.next;
             e2 = e2.next;
        }

        return (e1 == null && e2 == null);
    }

    /**
     * Return a string representation of the list.
     * @return a string representation of the list
     */
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("DoublyLinkedList[");
        for (Entry p = first; p != null; p = p.next ) {
            if (p != first)
                sb.append(",");
            sb.append(String.valueOf(p.data));
        }
        sb.append("]");
        return sb.toString();
    }

    private Entry first;
    private Entry last;
}
