1616import java .io .ByteArrayInputStream ;
1717import java .io .File ;
1818import java .io .IOException ;
19+ import java .io .RandomAccessFile ;
1920import java .net .URI ;
2021import java .nio .channels .FileChannel ;
22+ import java .nio .channels .FileLock ;
23+ import java .nio .channels .OverlappingFileLockException ;
2124import java .nio .charset .StandardCharsets ;
2225import java .nio .file .Files ;
2326import java .nio .file .Path ;
2427import java .nio .file .Paths ;
25- import java .nio .file .StandardOpenOption ;
2628import java .util .Arrays ;
2729import java .util .List ;
2830import java .util .Objects ;
3234import java .util .stream .StreamSupport ;
3335
3436import org .eclipse .esmf .ame .config .ApplicationSettings ;
35- import org .eclipse .esmf .ame .exceptions .FileNotFoundException ;
37+ import org .eclipse .esmf .ame .exceptions .FileHandlingException ;
38+ import org .eclipse .esmf .ame .exceptions .FileReadException ;
3639import org .eclipse .esmf .aspectmodel .loader .AspectModelLoader ;
3740import org .eclipse .esmf .aspectmodel .urn .AspectModelUrn ;
3841import org .eclipse .esmf .metamodel .AspectModel ;
@@ -58,26 +61,6 @@ public static ByteArrayInputStream createInputStream( final String turtleData )
5861 return new ByteArrayInputStream ( turtleData .getBytes ( StandardCharsets .UTF_8 ) );
5962 }
6063
61- /**
62- * Force remove the given file.
63- *
64- * @param file - that will be removed.
65- * @throws FileNotFoundException will be thrown if file can not be found.
66- */
67- public static void deleteFile ( @ Nonnull final File file ) {
68- try {
69- if ( !file .isDirectory () ) {
70- file .createNewFile ();
71- final FileChannel channel = FileChannel .open ( file .toPath (), StandardOpenOption .WRITE );
72- channel .lock ().release ();
73- channel .close ();
74- }
75- FileUtils .forceDelete ( file );
76- } catch ( final IOException e ) {
77- throw new FileNotFoundException ( String .format ( "File %s was not deleted successfully." , file .toPath () ), e );
78- }
79- }
80-
8164 /**
8265 * Finds and deletes all the parent folders that are empty for the given file.
8366 *
@@ -86,7 +69,7 @@ public static void deleteFile( @Nonnull final File file ) {
8669 public static void deleteEmptyFiles ( @ Nonnull final File file ) {
8770 if ( !ApplicationSettings .getEndFilePath ().toFile ().getName ().equals ( file .getName () ) ) {
8871 final File parentFile = file .getParentFile ();
89- deleteFile ( file );
72+ deleteFileSafely ( file );
9073
9174 final List <File > fileList = Arrays .stream ( Objects .requireNonNull ( parentFile .listFiles () ) )
9275 .filter ( f -> filterOutUnVisibleFiles ().test ( f ) ).toList ();
@@ -97,6 +80,60 @@ public static void deleteEmptyFiles( @Nonnull final File file ) {
9780 }
9881 }
9982
83+ /**
84+ * Safely deletes a file or directory. If the file is locked, it waits for the lock to be released
85+ * before attempting deletion. If the file is a directory, it deletes the directory and all its contents.
86+ *
87+ * @param file the file or directory to be deleted
88+ * @throws FileHandlingException if an I/O error occurs during deletion
89+ * @throws FileReadException if the thread is interrupted while waiting for the file lock to be released
90+ */
91+ public static void deleteFileSafely ( @ Nonnull final File file ) {
92+ try {
93+ if ( !file .exists () ) {
94+ return ;
95+ }
96+
97+ waitForFileUnlock ( file , 10 , 200 );
98+
99+ if ( !file .isDirectory () ) {
100+ Files .deleteIfExists ( file .toPath () );
101+ } else {
102+ FileUtils .deleteDirectory ( file );
103+ }
104+ } catch ( final IOException e ) {
105+ throw new FileHandlingException ( "File could not be deleted: " + file .getAbsolutePath (), e );
106+ } catch ( final InterruptedException e ) {
107+ Thread .currentThread ().interrupt ();
108+ throw new FileReadException ( "Interrupted while waiting to delete file: " + file .getAbsolutePath () );
109+ }
110+ }
111+
112+ private static void waitForFileUnlock ( final File file , final int maxRetries , final long sleepMillis ) throws InterruptedException {
113+ int retry = 0 ;
114+ while ( isFileLocked ( file ) ) {
115+ if ( retry ++ >= maxRetries ) {
116+ throw new RuntimeException ( "File is still locked after retries: " + file .getAbsolutePath () );
117+ }
118+ Thread .sleep ( sleepMillis );
119+ }
120+ }
121+
122+ private static boolean isFileLocked ( final File file ) {
123+ try ( final RandomAccessFile raf = new RandomAccessFile ( file , "rw" ); final FileChannel channel = raf .getChannel () ) {
124+
125+ final FileLock lock = channel .tryLock ();
126+ if ( lock != null ) {
127+ lock .release ();
128+ return false ;
129+ }
130+ } catch ( final IOException | OverlappingFileLockException e ) {
131+ return true ;
132+ }
133+
134+ return true ;
135+ }
136+
100137 private static Predicate <File > filterOutUnVisibleFiles () {
101138 return file -> !file .getName ().equals ( ".DS_Store" );
102139 }
@@ -168,8 +205,7 @@ public static Supplier<AspectModel> getAspectModelSupplier( final AspectModelUrn
168205 public static Supplier <AspectModel > getAspectModelSupplier ( final String turtleData , final File newFile ,
169206 final AspectModelLoader aspectModelLoader ) {
170207 final Optional <URI > sourceLocation = Optional .of ( newFile .toURI () );
171- final ByteArrayInputStream inputStream = new ByteArrayInputStream (
172- turtleData .getBytes ( StandardCharsets .UTF_8 ) );
208+ final ByteArrayInputStream inputStream = new ByteArrayInputStream ( turtleData .getBytes ( StandardCharsets .UTF_8 ) );
173209
174210 return createLazySupplier ( () -> aspectModelLoader .load ( inputStream , sourceLocation ) );
175211 }
0 commit comments