/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 * specific language governing permissions and limitations under the
 * License.  When distributing the software, include this License Header
 * Notice in each file and include the License file at
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the
 * License Header, with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * Contributor(s):
 *
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 *
 * If you wish your version of this file to be governed by only the CDDL
 * or only the GPL Version 2, indicate your decision by adding
 * "[Contributor] elects to include this software in this distribution
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 * single choice of license, a recipient has the option to distribute
 * your version of this file under either the CDDL, the GPL Version 2 or
 * to extend the choice of license to its licensees as provided above.
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 * Version 2 license, then the option applies only if the new code is
 * made subject to such option by the copyright holder.
 */
/*
 *	TestMerge - test the basic features.
 *
 */

import org.netbeans.modules.schema2beans.*;

import java.beans.*;

import book.*;


public class TestMerge extends BaseTest {
    public static void main(String[] argv) {
        BaseTest o = new TestMerge();
        if (argv.length > 0)
            o.setDocumentDir(argv[0]);
        try {
            o.run();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
        System.exit(0);
    }
    


    //
    // This is the reference number for memory consumption for 50 clones
    // (simply ran the test to get the number). We check against this number
    // to make sure that any change in schema2beans does not introduce a 
    // significant increase of mem usage.  (Note that sometimes JDK changes
    // can change this number too.)
    //
    static float memUsageReference = 5226;

    
    public class MyListener implements PropertyChangeListener {
        GraphManager	gm;
        String			listenerName;
        Object			oldValue;
        Object			newValue;
        String			propertyName;
        Object			source;
        int				index;
        boolean			mute;
        boolean			remove;
	
        public MyListener(BaseBean bean) {
            this.listenerName = bean.name();
            gm = bean.graphManager();
            this.remove = false;
            this.mute = false;
            out("new listener for " + this.listenerName);
        }
	
        public void reset() {
            this.oldValue = null;
            this.newValue = null;
            this.propertyName = null;
            this.source = null;
            this.index = -1;
        }
	
        public void propertyChange(PropertyChangeEvent e) {
            if (this.mute)
                return;
	    
            String k;
            this.oldValue = e.getOldValue();
            this.newValue = e.getNewValue();
            this.propertyName = e.getPropertyName();
            this.source = e.getSource();
            String n = this.propertyName;
            this.index = gm.getPropertyIndex(n);
	    
            if (this.newValue == null)
                k = "Rmv";
            else
                if (this.oldValue == null)
                    k = "Add";
                else
                    k = "Chg";
	    
            out("<" + k + " Lnr:" + this.listenerName + " Evt:" + n +
                " Src:" + this.source.getClass().getName() + ">");
            if (remove) {
                out("<" + gm.getPropertyName(n) + "[" + this.index +
                    "]" + " - Parent: " + gm.getPropertyParentName(n) + ">");
            } else {
                BaseBean propertyParent = gm.getPropertyParent(n);
                String parentClassName = propertyParent == null ? "<no class>" : propertyParent.getClass().getName();
                out("<" + gm.getPropertyName(n) + "[" + this.index +
                    "]" + " - Parent: " + gm.getPropertyParentName(n) +
                    "/" + parentClassName + ">");
            }
	    
            //out("received " + this.oldValue + "/" + this.newValue + "/" +
            //	this.propertyName);
        }
	
        public void removeMode() {
            this.remove = true;
        }
	
        public Object oldValue() {
            return this.oldValue;
        }
	
        public String stringOldValue() {
            if (this.oldValue == null)
                return "<null>";
            else
                return this.oldValue.toString();
        }
	
        public Object newValue() {
            return this.newValue;
        }
	
        public String stringNewValue() {
            if (this.newValue == null)
                return "<null>";
            else
                return this.newValue.toString();
        }
	
        public String name() {
            return this.propertyName;
        }
	
        public void mute(boolean mute) {
            this.mute = mute;
        }
	
        public String toString() {
            return this.name() + " raised from source " +
                this.source.getClass().getName();
        }
    }
    
    private MyListener l1, l2, l3;
    
    void mute(boolean mute) {
        if (l1 != null)
            l1.mute(mute);
        if (l2 != null)
            l2.mute(mute);
        if (l3 != null)
            l3.mute(mute);
    }
    
    public void run()
        throws Exception {
        Book b1, b2, b3;
	
        this.readDocument("tm01_g1.xml");
        out("creating the bean graph1");
        b1 = Book.createGraph(doc);
	
        this.readDocument("tm01_g1.xml");
        out("creating the bean graph2");
        b2 = Book.createGraph(doc);
	
        l1 = new MyListener(b1);
        b1.addPropertyChangeListener(l1);
	
        //	b2 should never change, so we should never receive anything
        //	on this listener
        l2 = new MyListener(b2);
        b2.addPropertyChangeListener(l2);
	
        //
        //	Merge two identical graphs
        //
        setTest("Merge(Update) - two identical xml files");
        out("should not get any event");
        b1.merge(b2);
        check(b1.toString().equals(b2.toString()), " - no diff");
        check(b1.isEqualTo(b2), " - equals");
	
        //	One mixed up document, but should be the same
        setTest("Merge(Update) - two identical xml files (one mixed up)");
        this.readDocument("tm01_g2.xml");
        out("creating the bean graph3");
        b3 = Book.createGraph(doc);
	
        l3 = new MyListener(b3);
        b3.addPropertyChangeListener(l3);
	
        b3.merge(b1);
        check(b3.isEqualTo(b1), " - equals");
	
        //
        //	Change only one element of the second graph
        //
        setTest("Merge(Update) - changing one property");
        String s1 = "This is the new summary";
        String s2 = b2.getSummary();
	
        //	g1/g2
        this.mute(true);
        b2.setSummary(s1);
        out("should get one event for Summary");
        this.mute(false);
        b1.merge(b2);
	
        b1.write(System.out);
	
        check(b2.isEqualTo(b1), " - equals");
        //	g1/g3
        this.mute(true);
        b1.setSummary(s2);
        this.mute(false);
        out("should not get any event");
        b1.merge(b3);
        check(b3.isEqualTo(b1), " - equals");
        this.mute(true);
        b2.setSummary(s2);
	
        //
        //	Mixing array of strings
        //
        setTest("Merge(Update) - mixing  array of strings");
        check(b1.getAuthor(0).equals(b2.getAuthor(0)), "initial match");
        check(b1.getAuthor(1).equals(b2.getAuthor(1)), "initial match");
        check(b1.getAuthor(2).equals(b2.getAuthor(2)), "initial match");
        this.mute(false);
        String [] ss1 = b2.getAuthor();
        String [] ss2 = new String[3];
        ss2[0] = ss1[2];
        ss2[1] = ss1[1];
        ss2[2] = ss1[0];
        b2.setAuthor(ss2);
        check(b1.getAuthor(0).equals(b2.getAuthor(2)), "mixed");
        check(b1.getAuthor(1).equals(b2.getAuthor(1)), "mixed");
        check(b1.getAuthor(2).equals(b2.getAuthor(0)), "mixed");
        out("should not get any event");
        b1.merge(b2);
        check(b2.isEqualTo(b1), " - equals");
        this.mute(true);
        b2.setAuthor(ss1);
        check(b1.getAuthor(0).equals(b2.getAuthor(0)), "reset match");
        check(b1.getAuthor(1).equals(b2.getAuthor(1)), "reset match");
        check(b1.getAuthor(2).equals(b2.getAuthor(2)), "reset match");
	
        //
        //	Mixing array of beans
        //
        setTest("Merge(Update) - mixing  array of beans");
        check(b1.getChapter(0).isEqualTo(b2.getChapter(0)), "initial match");
        check(b1.getChapter(1).isEqualTo(b2.getChapter(1)), "initial match");
        check(b1.getChapter(2).isEqualTo(b2.getChapter(2)), "initial match");
        this.mute(false);
        Chapter [] cc1 = b2.getChapter();
        Chapter [] cc2 = new Chapter[3];
        cc2[0] = cc1[2];
        cc2[1] = cc1[1];
        cc2[2] = cc1[0];
        b2.setChapter(cc2);
        check(b1.getChapter(0).isEqualTo(b2.getChapter(2)), "mixed");
        check(b1.getChapter(1).isEqualTo(b2.getChapter(1)), "mixed");
        check(b1.getChapter(2).isEqualTo(b2.getChapter(0)), "mixed");
        out("should not get any event");
        b1.merge(b2);
        check(b2.isEqualTo(b1), " - equals");
        this.mute(true);
        b2.setChapter(cc1);
        check(b1.getChapter(0).isEqualTo(b2.getChapter(0)), "reset match");
        check(b1.getChapter(1).isEqualTo(b2.getChapter(1)), "reset match");
        check(b1.getChapter(2).isEqualTo(b2.getChapter(2)), "reset match");
	
        //
        //	Removing a String elt from an array
        //
        setTest("Merge(Update) - remove elt from array of strings");
        check(b1.getAuthor(0).equals(b2.getAuthor(0)), "initial match");
        check(b1.getAuthor(1).equals(b2.getAuthor(1)), "initial match");
        check(b1.getAuthor(2).equals(b2.getAuthor(2)), "initial match");
        this.mute(false);
        s1 = b2.getAuthor(1);
        out("should get one remove event from deletion");
        b2.removeAuthor(s1);
        check(b1.getAuthor(0).equals(b2.getAuthor(0)), "match after rem");
        check(b1.getAuthor(2).equals(b2.getAuthor(1)), "match after rem");
        out("should get one remove event from merge");
        b1.merge(b2);
        check(b1.getAuthor(0).equals(b2.getAuthor(0)), "match");
        check(b1.getAuthor(1).equals(b2.getAuthor(1)), "match");
        check(b1.sizeAuthor() == b2.sizeAuthor(), "correct size");
        check(b2.isEqualTo(b1), " - equals");
	
        //
        //	Adding a String elt from an array
        //
        setTest("Merge(Update) - add elt from array of strings");
        this.mute(false);
        out("should get one event for elt added");
        b2.addAuthor(s1);
        check(b2.getAuthor(2).equals(s1), "added");
        out("should get one event for elt added from merge");
        //out("Here is b1:");
        //b1.writeNoReindent(System.out);
        //out("Here is b2:");
        //b2.writeNoReindent(System.out);
        b1.merge(b2);
        //out("Here is b1:");
        //b1.writeNoReindent(System.out);
        check(b1.getAuthor(0).equals(b2.getAuthor(0)), "match");
        check(b1.getAuthor(1).equals(b2.getAuthor(1)), "match");
        check(b1.getAuthor(2).equals(b2.getAuthor(2)), "match");
        check(b1.sizeAuthor() == b2.sizeAuthor(), "correct size");
        check(b2.isEqualTo(b1), " - equals");
	
        //
        //	Removing a Bean elt from an array
        //
        setTest("Merge(Update) - remove elt from array of beans");
        check(b1.getChapter(0).isEqualTo(b2.getChapter(0)), "initial match");
        check(b1.getChapter(1).isEqualTo(b2.getChapter(1)), "initial match");
        check(b1.getChapter(2).isEqualTo(b2.getChapter(2)), "initial match");
        this.mute(false);
        Chapter c1 = b2.getChapter(1);
        Chapter c2 = (Chapter)c1.clone();
        out("should get one remove event from deletion");
        b2.removeChapter(c1);
        check(b1.getChapter(0).isEqualTo(b2.getChapter(0)), "match after rem");
        check(b1.getChapter(2).isEqualTo(b2.getChapter(1)), "match after rem");
        out("should get one remove event from merge");
        b1.merge(b2);
        check(b1.getChapter(0).isEqualTo(b2.getChapter(0)), "match");
        check(b1.getChapter(1).isEqualTo(b2.getChapter(1)), "match");
        check(b1.sizeChapter() == b2.sizeChapter(), "correct size");
        check(b2.isEqualTo(b1), " - equals");
	
        //
        //	Adding a Bean elt from an array
        //
        setTest("Merge(Update) - add elt from array of strings");
        this.mute(false);
        out("should get one event for elt added");
        b2.addChapter(c2);
        check(b2.getChapter(2).isEqualTo(c2), "added");
        out("should get one event for elt added from merge");
        b1.merge(b2);
        check(b1.getChapter(0).isEqualTo(b2.getChapter(0)), "match");
        check(b1.getChapter(1).isEqualTo(b2.getChapter(1)), "match");
        check(b1.getChapter(2).isEqualTo(b2.getChapter(2)), "match");
        check(b1.sizeChapter() == b2.sizeChapter(), "correct size");
        check(b2.isEqualTo(b1), " - equals");
	
	
        //
        //	Compare two graphs with missing nodes and elements
        //
        Book b4, b5, b6;
	
        this.readDocument("tm01_g1.xml");
        out("creating the bean graph1");
        b4 = Book.createGraph(doc);
	
        b5 = (Book)b4.clone();
        b6 = (Book)b4.clone();
	
        setTest("comparing graphs with 1 null elts");
        check(b4.sizeChapter() == 3);
        check(b5.sizeChapter() == 3);
	
        //	bean[] full / bean null
        check(b4.isEqualTo(b5));
	
        setTest("comparing graphs with null indexed elts");
        //	g1.bean[] has 1 null / g2.bean[] has 1 null / bean null
        b4.setChapter(1, null);
        b5.setChapter(2, null);
        check(!b4.isEqualTo(b5));
        b4.merge(b5);
        check(b4.sizeChapter() == 3, "correct new array sise");
        check(b4.getChapter(0).isEqualTo(b5.getChapter(0)), "elt ok");
        check(b4.getChapter(1) == null, "elt ok");
        check(b4.getChapter(2).isEqualTo(b5.getChapter(1)), "elt ok");
	
        setTest("comparing graphs with null single bean elt");
        //	g1.bean non null / g2.bean is null
        b4 = (Book)b6.clone();
        b5 = (Book)b6.clone();
        Content ct = new Content();
        ct.setTitle("This is a title");
        ct.setComment("And this is a comment");
        check(b4.isEqualTo(b5));
        b5.setContent(ct);
        check(!b4.isEqualTo(b5));
        b4.merge(b5);
        check(b4.getContent().isEqualTo(b5.getContent()));
	
        //	Clone an element which is not part of a graph
        Chapter c3 = new Chapter();
        c3.setComment("This is a comment");
        c3.setNumber("123");
        c3.addParagraph("This is a new paragraph");
        Chapter c4 = (Chapter)c3.clone();
	
        //	Add both elements to two identical graphs - should get two
        //	identical graphs
        setTest("cloning a new bean");
        Book b7 = (Book)b2.clone();
        Book b8 = (Book)b2.clone();
        b7.addChapter(c3);
        b8.addChapter(c4);
        check(c3.isEqualTo(c4), "objects equal");
        check(b7.isEqualTo(b8), "same graph once added");
	
        //
        //	Test the attributes. When we merge graphs, we need to make
        //	sure that the attributes are also merged.
        //
        this.readDocument("tm01_g2.xml");
        out("creating the bean graph1");
        b1 = Book.createGraph(doc);
	
        //	g3 and g2 elements are identicals, g3 has attributes,
        //	g2 has not.
        this.readDocument("tm01_g3.xml");
        out("creating the bean graph2");
        b2 = Book.createGraph(doc);

        // Make sure that we can clone a part of the graph without loosing
        // the attributes.
        BaseBean bb = b2.getChapter(0);
        //  Should see the chapter attribute
        out(bb.dumpDomNode());

        b3 = (Book)b1.clone();
        int index = b3.addValue("Chapter", bb.clone());
        bb = b3.getChapter(index);
        //  Should see the chapter attribute on this cloned element
        out(bb.dumpDomNode());

        l1 = new MyListener(b1);
        l1.mute(true);
        b1.addPropertyChangeListener(l1);
	
        //	b2 should never change, so we should never receive anything
        //	on this listener
        l2 = new MyListener(b2);
        b2.addPropertyChangeListener(l2);

        //	The only events we should have when we merge are the 
        //	attributes events, because the graphs only differ by attr.
        setTest("Merging attributes");
        check(!b1.isEqualTo(b2), "shouldn't be equals (diff an attr)");
        //GraphManager.debug(true);
        b1.merge(b2);
        check(b1.isEqualTo(b2), "should be equals");
	
        //	Make sure that b1 has the attributes
        s1 = b1.getAttributeValue("good");
        check(s1 != null, "attr on root != null");
        if (s1 != null) {
            check(s1.equals("no"), "attr on root");
        }
        s1 = b1.getAttributeValue("summary", "length");
        check(s1 != null, "attr on summary != null");
        if (s1 != null) {
            check(s1.equals("123"), "attr on summary");
        }
        s1 = b1.getAttributeValue("summary", "lang");
        check(s1 != null, "attr on summary != null");
        if (s1 != null) {
            check(s1.equals("us"), "attr on summary");
        }

        s1 = b1.getAttributeValue("chapter", 1, "title");
        out(s1);
        check(s1 != null, "attr on chapter != null");
        if (s1 != null) {
            check(s1.equals("First"), "attr on chapter");
        }

        s1 = b1.getAttributeValue("chapter", 2, "title");
        out(s1);
        check(s1 != null, "attr on chapter != null");
        if (s1 != null) {
            check(s1.equals("Second"), "attr on chapter");
        }
	
        //
        //  Make sure that we do not consume too much memory
        //

//        //  Ignore the first one
//        this.getKMemUsage();
//
//        int k1 = this.getKMemUsage();
//		
//        Book newBook;
//
//        this.readDocument("tm01_g3.xml");
//        out("creating the bean graph for memory test");
//        newBook = Book.createGraph(doc);
//		
//        int maxLoop = 50;
//        BaseBean[] aBook = new BaseBean[maxLoop];
//        for(int loop=0; loop<maxLoop; loop++) {
//            aBook[loop] = (BaseBean)newBook.clone();
//        }
//
//        int k2 = this.getKMemUsage() - k1;
//
//        float diff = (float)(k2 - memUsageReference);
//
//        if (diff > 0) {
//            //	We consume more memory than excepted
//            float p = diff/memUsageReference*100;
//            if (p > 20.0) {
//                out("It seems that the last schema2beans code changes have increased the memory consumption by " + p + "%");
//                out("If this is expected and acceptable, change the memUsageReference value in TestMerge.java, to be " + k2);
//            }
//        } else {
///*            //	We consume less memory than expected
//            float p = Math.abs(diff)/memUsageReference*100;
//            if (p > 25.0) {
//                out("It seems that the last schema2beans code changes have decreased the memory consumption by " + p + "% !!!");
//                out("Please, change the memUsageReference value in TestMerge.java, to be " + k2);
//            }   */
//        }
//        out("memory test done");

        readDocument("tm01_g1.xml");
        b1 = Book.createGraph(doc);
        readDocument("tm01_g4.xml");
        out("creating the bean graph for the comment merge test");
        Book commentedGraph = Book.createGraph(doc);
        b1.merge(commentedGraph);
        out(b1);
    }
}


