/*
 * Copyright 2019 Google LLC.
 *
 * 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 com.google.cloud.tools.jib.filesystem;

import com.google.common.base.Preconditions;
import java.io.Closeable;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/** Creates and deletes lock files. */
public class LockFile implements Closeable {

  private static final ConcurrentHashMap<Path, Lock> lockMap = new ConcurrentHashMap<>();

  private final Path lockFile;
  private final FileLock fileLock;

  private LockFile(Path lockFile, FileLock fileLock) {
    this.lockFile = lockFile;
    this.fileLock = fileLock;
  }

  /**
   * Creates a lock file.
   *
   * @param lockFile the path of the lock file
   * @return a new {@link LockFile} that can be released later
   * @throws IOException if creating the lock file fails
   */
  public static LockFile lock(Path lockFile) throws IOException {
    try {
      // This first lock is to prevent multiple threads from calling FileChannel.lock(), which would
      // otherwise throw OverlappingFileLockException
      lockMap.computeIfAbsent(lockFile, key -> new ReentrantLock()).lockInterruptibly();

    } catch (InterruptedException ex) {
      throw new IOException("Interrupted while trying to acquire lock", ex);
    }

    Files.createDirectories(lockFile.getParent());
    FileLock fileLock = new FileOutputStream(lockFile.toFile()).getChannel().lock();
    return new LockFile(lockFile, fileLock);
  }

  /** Releases the lock file. */
  @Override
  public void close() {
    try {
      fileLock.release();

    } catch (IOException ex) {
      throw new IllegalStateException("Unable to release lock", ex);

    } finally {
      try {
        Preconditions.checkNotNull(lockMap.get(lockFile)).unlock();
        Files.delete(lockFile);

      } catch (IllegalMonitorStateException | IOException ignored) {
      }
    }
  }
}

@@ -770,16 +770,45 @@
 eption;%0A
+import java.io.OutputStream;%0A
 import j
@@ -1267,16 +1267,59 @@
 ileLock;
+%0A  private final OutputStream outputStream;
 %0A%0A  priv
@@ -1363,16 +1363,43 @@
 fileLock
+, OutputStream outputStream
 ) %7B%0A    
@@ -1454,16 +1454,54 @@
 leLock;%0A
+    this.outputStream = outputStream;%0A
   %7D%0A%0A  /
@@ -2211,29 +2211,41 @@
     File
-Lock fileLock
+OutputStream outputStream
  = new F
@@ -2278,16 +2278,87 @@
 oFile())
+;%0A    FileLock fileLock = null;%0A    try %7B%0A      fileLock = outputStream
 .getChan
@@ -2375,16 +2375,18 @@
 ();%0A    
+ 
+ 
 return n
@@ -2415,18 +2415,123 @@
 fileLock
-);
+, outputStream);%0A%0A    %7D finally %7B%0A      if (fileLock == null) %7B%0A        outputStream.close();%0A      %7D%0A    %7D
 %0A  %7D%0A%0A  
@@ -2632,16 +2632,44 @@
 lease();
+%0A      outputStream.close();
 %0A%0A    %7D 
@@ -2783,22 +2783,8 @@
 y %7B%0A
-      try %7B%0A  
     
@@ -2849,118 +2849,8 @@
 ();%0A
-        Files.delete(lockFile);%0A%0A      %7D catch (IllegalMonitorStateException %7C IOException ignored) %7B%0A      %7D%0A
     

Fix lock file failures (#1696)