fr.quatrevieux.singleinstance.LockFile.Reader
last analyzed

Complexity

Total Complexity 0

Size/Duplication

Total Lines 3
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 0
c 1
b 0
f 0
dl 0
loc 3
ccs 0
cts 0
cp 0

1 Method

Rating   Name   Duplication   Size   Complexity  
read(DataInput) 0 1 ?
1
/*
2
 * This file is part of SingleInstance.
3
 *
4
 * SingleInstance is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU Lesser General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * SingleInstance is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public License
15
 * along with SingleInstance.  If not, see <https://www.gnu.org/licenses/>.
16
 *
17
 * Copyright (c) 2020 Vincent Quatrevieux
18
 */
19
20
package fr.quatrevieux.singleinstance;
21
22
import java.io.*;
23
import java.nio.channels.FileLock;
24
import java.util.HashMap;
25
import java.util.Map;
26
import java.util.Objects;
27
28
/**
29
 * Handle lock file
30
 * A lock file can be acquire to block all other process, until it's released
31
 *
32
 * <pre>{@code
33
 *     LockFile lock = new LockFile("app.lock");
34
 *
35
 *     if (!lock.acquire()) {
36
 *         System.out.println("An other instance is running");
37
 *         System.exit(1);
38
 *     }
39
 *
40
 *     Runtime.getRuntime().addShutdownHook(new Thread(lock::release));
41
 * }</pre>
42
 */
43
final public class LockFile {
44
    @FunctionalInterface
45
    public interface Writer {
46
        public void write(DataOutput output) throws IOException;
47
    }
48
49
    @FunctionalInterface
50
    public interface Reader<R> {
51
        public R read(DataInput input) throws IOException;
52
    }
53
54
    /**
55
     * Handle sharing the FileLock instances
56
     */
57
    static private class SharedLock {
58
        @FunctionalInterface
59
        private interface FileLockFactory {
60
            public FileLock create() throws IOException;
61
        }
62
63
        /**
64
         * Store all lock instances
65
         */
66 1
        final static private Map<File, SharedLock> instances = new HashMap<>();
67
68
        final private File file;
69
        final private FileLock fileLock;
70 1
        private int count = 1;
71
72 1
        public SharedLock(File file, FileLock fileLock) {
73 1
            this.file = file;
74 1
            this.fileLock = fileLock;
75 1
        }
76
77
        /**
78
         * Try to remove the shared lock
79
         *
80
         * @return true if the lock is removed
81
         */
82
        private boolean remove() throws IOException {
83 1
            synchronized (instances) {
84 1
                --count;
85
86 1
                if (count <= 0) {
87 1
                    fileLock.release();
88 1
                    instances.remove(file);
89 1
                    return true;
90
                }
91
92 1
                return false;
93
            }
94
        }
95
96
        /**
97
         * Create the SharedLock instance
98
         *
99
         * @return The lock instance, or null if cannot get the lock
100
         */
101
        static private SharedLock create(File file, FileLockFactory factory) throws IOException {
102 1
            synchronized (instances) {
103 1
                SharedLock sharedLock = instances.get(file);
104
105 1
                if (sharedLock != null) {
106 1
                    ++sharedLock.count;
107 1
                    return sharedLock;
108
                }
109
110 1
                FileLock lock = factory.create();
111
112 1
                if (lock == null) {
113
                    return null;
114
                }
115
116 1
                sharedLock = new SharedLock(file, lock);
117 1
                instances.put(file, sharedLock);
118
119 1
                return sharedLock;
120
            }
121
        }
122
    }
123
124
    final static public String DEFAULT_FILENAME = ".lock";
125
126
    final private File file;
127
128
    private RandomAccessFile randomAccessFile;
129
    private SharedLock lock;
130
131
    /**
132
     * Create the lock file using the default file name
133
     */
134
    public LockFile() {
135
        this(DEFAULT_FILENAME);
136
    }
137
138
    public LockFile(String filename) {
139 1
        this(new File(filename));
140 1
    }
141
142 1
    public LockFile(File file) {
143 1
        this.file = file.getAbsoluteFile();
144 1
    }
145
146
    /**
147
     * Acquire the lock file
148
     * This method is not blocking : if the lock file is already acquired, false is immediately returned
149
     *
150
     * @return true if the lock is acquired, or false if the lock is already acquired
151
     * @throws IOException When lock file creation failed
152
     */
153
    public boolean acquire() throws IOException {
154 1
        if (lock == null) {
155 1
            lock = SharedLock.create(file, () -> file().getChannel().tryLock());
156
        }
157
158 1
        return lock != null;
159
    }
160
161
    /**
162
     * Wait until lock is release, and then acquire the lock file
163
     * This method is blocking
164
     *
165
     * @throws IOException When lock file creation failed
166
     */
167
    public void lock() throws IOException {
168 1
        if (lock == null) {
169 1
            lock = SharedLock.create(file, () -> file().getChannel().lock());
170
        }
171 1
    }
172
173
    /**
174
     * Release the lock file
175
     * This method will never fail
176
     */
177
    public void release() {
178
        try {
179 1
            boolean deleteFile = false;
180
181 1
            if (lock != null) {
182 1
                deleteFile = lock.remove();
183 1
                lock = null;
184
            }
185
186 1
            if (randomAccessFile != null) {
187 1
                randomAccessFile.close();
188 1
                randomAccessFile = null;
189
            }
190
191 1
            if (deleteFile) {
192 1
                file.delete();
193
            }
194
        } catch (IOException e) {
195
            // ignore
196 1
        }
197 1
    }
198
199
    /**
200
     * Write data to the lock file
201
     * The file is truncated before write
202
     *
203
     * @param writer Write operation
204
     * @throws IOException When write failed
205
     */
206
    public void write(Writer writer) throws IOException {
207 1
        if (!acquire()) {
208
            throw new IllegalStateException("Cannot acquire write access to lock file");
209
        }
210
211 1
        randomAccessFile.getChannel().truncate(0);
212 1
        writer.write(randomAccessFile);
213 1
    }
214
215
    /**
216
     * Read data from lock file
217
     * The cursor is set to start of the file before read
218
     *
219
     * @param reader The data reader
220
     * @param <R> The return type
221
     *
222
     * @return The read data
223
     * @throws IOException When read failed
224
     */
225
    public <R> R read(Reader<R> reader) throws IOException {
226 1
        final RandomAccessFile file = file();
227
228 1
        file.seek(0);
229
230 1
        return reader.read(file());
231
    }
232
233
    @Override
234
    protected void finalize() throws Throwable {
235
        super.finalize();
236
237
        release();
238
    }
239
240
    @Override
241
    public boolean equals(Object o) {
242 1
        if (this == o) {
243 1
            return true;
244
        }
245 1
        if (o == null || getClass() != o.getClass()) {
246 1
            return false;
247
        }
248
249 1
        LockFile lockFile = (LockFile) o;
250 1
        return file.equals(lockFile.file);
251
    }
252
253
    @Override
254
    public int hashCode() {
255 1
        return Objects.hash(file);
256
    }
257
258
    private RandomAccessFile file() throws FileNotFoundException {
259 1
        if (randomAccessFile == null) {
260 1
            randomAccessFile = new RandomAccessFile(file, "rw");
261
        }
262
263 1
        return randomAccessFile;
264
    }
265
}
266