/*
 * Copyright 2016 peter.
 *
 * Licensed 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 onl.area51.filesystem.io.overlay;

import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Utility class used to ensure we handle some operation on a path one thread at a time
 *
 * @author peter
 */
public class PathSynchronizer
        implements Closeable
{

    private final Lock lock = new ReentrantLock();
    private final Map<String, Condition> conditions = new HashMap<>();

    private final ExecutorService executorService;

    public PathSynchronizer()
    {
        this( Executors.newSingleThreadExecutor() );
    }

    public PathSynchronizer( int parallelism )
    {
        this( parallelism == 0 ? Executors.newWorkStealingPool() : Executors.newWorkStealingPool( parallelism ) );
    }

    public PathSynchronizer( ExecutorService executor )
    {
        this.executorService = executor;
    }

    public final ExecutorService getExecutorService()
    {
        return executorService;
    }

    public final void execute( String key, Callable<Void> t )
            throws IOException
    {
        lock.lock();
        try {
            Condition c = conditions.get( key );
            if( c == null ) {
                c = lock.newCondition();
                conditions.put( key, c );
                lock.unlock();
                try {
                    Future<Void> f = executorService.submit( t );
                    f.get();
                }
                finally {
                    lock.lock();
                    c.signalAll();
                    conditions.remove( key );
                }
            }
            else {
                c.await();
            }
        }
        catch( InterruptedException ex ) {
            throw new IOException( ex );
        }
        catch( ExecutionException ex ) {
            Throwable tr = ex.getCause();
            if( tr instanceof IOException ) {
                throw (IOException) tr;
            }
            if( tr instanceof UncheckedIOException ) {
                throw ((UncheckedIOException) tr).getCause();
            }
            else {
                throw new IOException( tr );
            }
        }
        finally {
            lock.unlock();
        }
    }

    public final void execute( char[] path, Callable<Void> t )
            throws IOException
    {
        if( path == null || path.length == 0 ) {
            throw new FileNotFoundException( "/" );
        }
        execute( String.valueOf( path ), t );
    }

    @Override
    public void close()
            throws IOException
    {
        executorService.shutdownNow();

        lock.lock();
        try {
            conditions.values().forEach( Condition::signalAll );
        }
        finally {
            lock.unlock();
        }
    }

}

@@ -2101,32 +2101,40 @@
 k();%0A        try
+%0A       
  %7B%0A            C
@@ -2196,16 +2196,28 @@
 = null )
+%0A           
  %7B%0A     
@@ -2336,32 +2336,48 @@
              try
+%0A               
  %7B%0A             
@@ -2491,32 +2491,48 @@
          finally
+%0A               
  %7B%0A             
@@ -2672,32 +2672,44 @@
             else
+%0A           
  %7B%0A             
@@ -2741,32 +2741,24 @@
  %7D%0A        %7D
-%0A       
  catch( Inte
@@ -2770,32 +2770,40 @@
 edException ex )
+%0A       
  %7B%0A            t
@@ -2839,24 +2839,16 @@
        %7D
-%0A       
  catch( 
@@ -2870,16 +2870,24 @@
 ion ex )
+%0A       
  %7B%0A     
@@ -2953,16 +2953,25 @@
 tanceof 
+Unchecked
 IOExcept
@@ -2967,34 +2967,32 @@
 kedIOException )
- %7B
 %0A               
@@ -2992,35 +2992,9 @@
     
-    throw (IOException) tr;
+%7B
 %0A   
@@ -3006,40 +3006,34 @@
     
-%7D%0A
     
-        if( tr instanceof 
+IOException cause = ((
 Unch
@@ -3048,20 +3048,33 @@
 xception
- ) %7B
+) tr).getCause();
 %0A       
@@ -3092,45 +3092,49 @@
 row 
-((UncheckedIOException) tr).getC
+new IOException(cause.getMessage(), c
 ause
-(
 );%0A 
@@ -3162,16 +3162,28 @@
     else
+%0A           
  %7B%0A     
@@ -3218,16 +3218,32 @@
 tion( tr
+.getMessage(),tr
  );%0A    
@@ -3269,32 +3269,40 @@
 %0A        finally
+%0A       
  %7B%0A            l
@@ -3478,16 +3478,24 @@
 h == 0 )
+%0A       
  %7B%0A     
@@ -3751,16 +3751,24 @@
      try
+%0A       
  %7B%0A     
@@ -3852,16 +3852,24 @@
  finally
+%0A       
  %7B%0A     

Fix stacktrace when failing within the worker thread