2121import javax .inject .Named ;
2222import javax .inject .Singleton ;
2323
24- import java .io .ByteArrayInputStream ;
2524import java .io .ByteArrayOutputStream ;
2625import java .io .File ;
27- import java .io .FileInputStream ;
2826import java .io .IOException ;
29- import java .io .RandomAccessFile ;
3027import java .io .UncheckedIOException ;
28+ import java .nio .ByteBuffer ;
29+ import java .nio .channels .Channels ;
3130import java .nio .channels .FileChannel ;
3231import java .nio .channels .FileLock ;
3332import java .nio .channels .OverlappingFileLockException ;
3433import java .nio .file .Files ;
34+ import java .nio .file .NoSuchFileException ;
35+ import java .nio .file .Path ;
36+ import java .nio .file .StandardOpenOption ;
3537import java .util .Map ;
3638import java .util .Properties ;
3739
4547 * to back off two parallel implementations that coexist in Maven (this class and {@code maven-compat} one), as in
4648 * certain cases the two implementations may collide on properties files. This locking must remain in place for as long
4749 * as {@code maven-compat} code exists.
50+ *
51+ * <em>IMPORTANT:</em> This class is kept fully in sync with the master branch one (w/ simple change to convert File
52+ * to Path instances).
4853 */
4954@ Singleton
5055@ Named
@@ -53,15 +58,18 @@ public final class DefaultTrackingFileManager implements TrackingFileManager {
5358
5459 @ Override
5560 public Properties read (File file ) {
56- if (Files .isReadable (file .toPath ())) {
57- synchronized (getMutex (file )) {
58- try (FileInputStream stream = new FileInputStream (file );
59- FileLock unused = fileLock (stream .getChannel (), Math .max (1 , file .length ()), true )) {
61+ Path path = file .toPath ();
62+ if (Files .isReadable (path )) {
63+ synchronized (mutex (path )) {
64+ try (FileChannel fileChannel = FileChannel .open (path , StandardOpenOption .READ );
65+ FileLock unused = fileLock (fileChannel , true )) {
6066 Properties props = new Properties ();
61- props .load (stream );
67+ props .load (Channels . newInputStream ( fileChannel ) );
6268 return props ;
69+ } catch (NoSuchFileException e ) {
70+ LOGGER .debug ("No such file to read {}: {}" , path , e .getMessage ());
6371 } catch (IOException e ) {
64- LOGGER .warn ("Failed to read tracking file '{}'" , file , e );
72+ LOGGER .warn ("Failed to read tracking file '{}'" , path , e );
6573 throw new UncheckedIOException (e );
6674 }
6775 }
@@ -71,22 +79,20 @@ public Properties read(File file) {
7179
7280 @ Override
7381 public Properties update (File file , Map <String , String > updates ) {
74- Properties props = new Properties ();
75-
82+ Path path = file .toPath ();
7683 try {
77- Files .createDirectories (file . getParentFile (). toPath ());
84+ Files .createDirectories (path . getParent ());
7885 } catch (IOException e ) {
79- LOGGER .warn ("Failed to create tracking file parent '{}'" , file , e );
86+ LOGGER .warn ("Failed to create tracking file parent '{}'" , path , e );
8087 throw new UncheckedIOException (e );
8188 }
82-
83- synchronized (getMutex (file )) {
84- try (RandomAccessFile raf = new RandomAccessFile (file , "rw" );
85- FileLock unused = fileLock (raf .getChannel (), Math .max (1 , raf .length ()), false )) {
86- if (raf .length () > 0 ) {
87- byte [] buffer = new byte [(int ) raf .length ()];
88- raf .readFully (buffer );
89- props .load (new ByteArrayInputStream (buffer ));
89+ synchronized (mutex (path )) {
90+ try (FileChannel fileChannel = FileChannel .open (
91+ path , StandardOpenOption .READ , StandardOpenOption .WRITE , StandardOpenOption .CREATE );
92+ FileLock unused = fileLock (fileChannel , false )) {
93+ Properties props = new Properties ();
94+ if (fileChannel .size () > 0 ) {
95+ props .load (Channels .newInputStream (fileChannel ));
9096 }
9197
9298 for (Map .Entry <String , String > update : updates .entrySet ()) {
@@ -97,46 +103,54 @@ public Properties update(File file, Map<String, String> updates) {
97103 }
98104 }
99105
100- LOGGER .debug ("Writing tracking file '{}'" , file );
106+ LOGGER .debug ("Writing tracking file '{}'" , path );
101107 ByteArrayOutputStream stream = new ByteArrayOutputStream (1024 * 2 );
102108 props .store (
103109 stream ,
104110 "NOTE: This is a Maven Resolver internal implementation file"
105111 + ", its format can be changed without prior notice." );
106- raf .seek (0L );
107- raf .write (stream .toByteArray ());
108- raf .setLength (raf .getFilePointer ());
112+ fileChannel .position (0 );
113+ int written = fileChannel .write (ByteBuffer .wrap (stream .toByteArray ()));
114+ fileChannel .truncate (written );
115+ return props ;
109116 } catch (IOException e ) {
110- LOGGER .warn ("Failed to write tracking file '{}'" , file , e );
117+ LOGGER .warn ("Failed to write tracking file '{}'" , path , e );
111118 throw new UncheckedIOException (e );
112119 }
113120 }
121+ }
114122
115- return props ;
123+ @ Override
124+ public boolean delete (File file ) {
125+ Path path = file .toPath ();
126+ if (Files .isReadable (path )) {
127+ synchronized (mutex (path )) {
128+ try (FileChannel fileChannel = FileChannel .open (path , StandardOpenOption .WRITE );
129+ FileLock unused = fileLock (fileChannel , false )) {
130+ Files .delete (path );
131+ return true ;
132+ } catch (NoSuchFileException e ) {
133+ LOGGER .debug ("No such file to delete {}: {}" , path , e .getMessage ());
134+ } catch (IOException e ) {
135+ LOGGER .warn ("Failed to delete tracking file '{}'" , path , e );
136+ throw new UncheckedIOException (e );
137+ }
138+ }
139+ }
140+ return false ;
116141 }
117142
118- private Object getMutex ( File file ) {
143+ private Object mutex ( Path path ) {
119144 // The interned string of path is (mis)used as mutex, to exclude different threads going for same file,
120145 // as JVM file locking happens on JVM not on Thread level. This is how original code did it ¯\_(ツ)_/¯
121- /*
122- * NOTE: Locks held by one JVM must not overlap and using the canonical path is our best bet, still another
123- * piece of code might have locked the same file (unlikely though) or the canonical path fails to capture file
124- * identity sufficiently as is the case with Java 1.6 and symlinks on Windows.
125- */
126- try {
127- return file .getCanonicalPath ().intern ();
128- } catch (IOException e ) {
129- LOGGER .warn ("Failed to canonicalize path {}" , file , e );
130- // TODO This is code smell and deprecated
131- return file .getAbsolutePath ().intern ();
132- }
146+ return path .toAbsolutePath ().normalize ().toString ().intern ();
133147 }
134148
135- private FileLock fileLock (FileChannel channel , long size , boolean shared ) throws IOException {
149+ private FileLock fileLock (FileChannel channel , boolean shared ) throws IOException {
136150 FileLock lock = null ;
137151 for (int attempts = 8 ; attempts >= 0 ; attempts --) {
138152 try {
139- lock = channel .lock (0 , size , shared );
153+ lock = channel .lock (0 , Long . MAX_VALUE , shared );
140154 break ;
141155 } catch (OverlappingFileLockException e ) {
142156 if (attempts <= 0 ) {
0 commit comments