/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.openide.text;



import java.io.IOException;
import javax.swing.text.*;
import junit.textui.TestRunner;
import org.netbeans.junit.*;
import org.openide.util.Exceptions;
import org.openide.util.RequestProcessor;

/**
 * Exception during load of the document can cause starvation
 * in the thread that waits for that to happen.
 *
 * @author  Petr Nejedly, Jaroslav Tulach
 */
public class Deadlock40766Test extends NbTestCase implements CloneableEditorSupport.Env {
    static {
        System.setProperty("org.openide.windows.DummyWindowManager.VISIBLE", "false");
    }
    /** the support to work with */
    private CES support;
    // Env variables
    private String content = "Hello";
    private boolean valid = true;
    private boolean modified = false;
    /** if not null contains message why this document cannot be modified */
    private String cannotBeModified;
    private java.util.Date date = new java.util.Date ();
    private java.util.List/*<java.beans.PropertyChangeListener>*/ propL = new java.util.ArrayList ();
    private java.beans.VetoableChangeListener vetoL;
    
    /** Creates new TextTest */
    public Deadlock40766Test(String s) {
        super(s);
    }
    
    public static void main(String[] args) {
        TestRunner.run(new NbTestSuite(Deadlock40766Test.class));
    }

    protected void setUp () {
        support = new CES (this, org.openide.util.Lookup.EMPTY);
    }
    
    RequestProcessor my = new RequestProcessor("my");
    
    public void testDeadlock40766() throws Exception {
        org.openide.util.Task task;
        
        synchronized (support.helperLock) {
            my.post (support);
            // wait for the support (another thread) to try to open and block
            support.helperLock.wait ();
        }
        
        // now the RP if after the doc test but have not locked
        // let's change the state now.
        
        StyledDocument doc = support.openDocument();
        
        synchronized (support.helperLock) {
            // wait till it gets into support lock
            support.helperLock.notifyAll();
            support.helperLock.wait (1000);
        }

        // now the RP holds lock but doesn't have doc readAccess
        
        NbDocument.runAtomic(doc, new Runnable() {
            public void run() {
                synchronized (support.helperLock) {
                    support.helperLock.notifyAll();
                }
                
                try {
                    support.openDocument();
                } catch (IOException ioe) {
                    fail(ioe.getMessage());
                }
                
            }
        });
        
    }
    
    /* #38013 was a deadlock where:
     * 1) one thread started adding PositionRef before the document was loaded,
     *    slept for a while on a synchronized and awakened with newly load
     *    documet that it needed to readlock.
     * 2) second thread loaded the document and wanted to convert positions
     *    from inside its writelock.
     *
     * Reproduction:
     * 1. Start thread A, let it try to add PositionRef without a doc.
     *    As soon as it acquires PR$M lock, switch to B
     * 2. Start thread B, wait for A thread's rendezvous, start loading
     *    document (which spawns thread C)
     *    after 1000ms unblock thread A (C should already have locked document)
     */
    public void testDeadlock38013() throws Exception {
        org.openide.util.Task task;

        // this is thread B
        
        synchronized (support.helperLock) {
            my.post (support); // thread A
            support.helperLock.wait ();
            // we've got the beforeLock notification, we need the "after" one
            // so let's respin the locks
            support.helperLock.notifyAll();
            support.helperLock.wait ();
        }
        //now, B have the RP.M's lock and we have 1000 ms to lock
        // the document and try to get PR.M's lock from C
                
        StyledDocument doc = support.openDocument();
        
    }

    public void testCreatePositionCanBeCalledFromWriteLockOnDocument () throws Exception {
        final StyledDocument doc = support.openDocument ();
        
        class R implements Runnable {
            boolean inAtomic;
            PositionRef ref;
            
            public void run () {
                if (!inAtomic) {
                    inAtomic = true;
                    NbDocument.runAtomic (doc, this);
                    return;
                }
                
                synchronized (this) {
                    notifyAll ();
                    try {
                        wait (1000);
                    } catch (InterruptedException ex) {
                        fail (ex.getMessage ());
                    }
                }
                ref = support.createPositionRef (0, Position.Bias.Backward);
            }
        }
        
        RequestProcessor.Task task;
        R r = new R ();
        synchronized (r) {
            task = RequestProcessor.getDefault ().post (r);
            r.wait ();
        }
        
        // now R holds write lock on the document, and will wake up soon
        // grab the lock from oposite site
        PositionRef ref = support.createPositionRef (1, Position.Bias.Backward);
        
        assertNotNull ("Ref created", ref);
        task.waitFinished ();
        assertNotNull ("Ref1 crated", r.ref);
        
    } // end of testCreatePositionCanBeCalledFromWriteLockOnDocument
    
    //
    // Implementation of the CloneableEditorSupport.Env
    //
    
    public synchronized void addPropertyChangeListener(java.beans.PropertyChangeListener l) {
        propL.add (l);
    }    
    public synchronized void removePropertyChangeListener(java.beans.PropertyChangeListener l) {
        propL.remove (l);
    }
    
    public synchronized void addVetoableChangeListener(java.beans.VetoableChangeListener l) {
        assertNull ("This is the first veto listener", vetoL);
        vetoL = l;
    }
    public void removeVetoableChangeListener(java.beans.VetoableChangeListener l) {
        assertEquals ("Removing the right veto one", vetoL, l);
        vetoL = null;
    }
    
    public org.openide.windows.CloneableOpenSupport findCloneableOpenSupport() {
        return support;
    }
    
    public String getMimeType() {
        return "text/plain";
    }
    
    public java.util.Date getTime() {
        return date;
    }
    
    public java.io.InputStream inputStream() throws java.io.IOException {
        return new java.io.ByteArrayInputStream (content.getBytes ());
    }
    public java.io.OutputStream outputStream() throws java.io.IOException {
        class ContentStream extends java.io.ByteArrayOutputStream {
            public void close () throws java.io.IOException {
                super.close ();
                content = new String (toByteArray ());
            }
        }
        
        return new ContentStream ();
    }
    
    public boolean isValid() {
        return valid;
    }
    
    public boolean isModified() {
        return modified;
    }

    public void markModified() throws java.io.IOException {
        if (cannotBeModified != null) {
            IOException e = new IOException ();
            Exceptions.attachLocalizedMessage(e, cannotBeModified);
            throw e;
        }
        
        modified = true;
    }
    
    public void unmarkModified() {
        modified = false;
    }

    /** Implementation of the CES */
    private final class CES extends CloneableEditorSupport  implements Runnable {
        
        public CES (Env env, org.openide.util.Lookup l) {
            super (env, l);
        }
        
        protected String messageName() {
            return "Name";
        }
        
        protected String messageOpened() {
            return "Opened";
        }
        
        protected String messageOpening() {
            return "Opening";
        }
        
        protected String messageSave() {
            return "Save";
        }
        
        protected String messageToolTip() {
            return "ToolTip";
        }        
        
        
        protected StyledDocument createStyledDocument (EditorKit kit) {
            class Doc extends DefaultStyledDocument implements NbDocument.WriteLockable {
                public void runAtomic (Runnable r) {
                    writeLock();
                    try {
                        r.run();
                    } finally {
                        writeUnlock();
                    }
                }
                public void runAtomicAsUser (Runnable r) {
                    runAtomic(r);
                }
            }
            StyledDocument sd = new Doc();
            return sd;
        }

        Object helperLock = new Object();

        void howToReproduceDeadlock40766(boolean beforeLock) {
            if (my.isRequestProcessorThread()) {
                synchronized(helperLock) {
                    try {
                        helperLock.notifyAll();
                        helperLock.wait(1000);
                    } catch (InterruptedException ie) {
                        fail (ie.getMessage ());
                    }
                }
            }
        }
        
        public void run () {
            createPositionRef(0, Position.Bias.Forward);
        }

    } // end of CES
}
