Skip to content
This repository was archived by the owner on Jul 31, 2022. It is now read-only.

Commit 5f7a198

Browse files
committed
Optimize loading for files
1 parent d76f042 commit 5f7a198

File tree

4 files changed

+171
-109
lines changed

4 files changed

+171
-109
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.kttdevelopment.simplehttpserver.handler;
2+
3+
/**
4+
* Determines how files will be loaded in the {@link FileHandler}. <br>
5+
*
6+
* <code>PRELOAD</code> - read file when it is added to the handler <br>
7+
* <code>WATCHLOAD</code> - read file when it is added and anytime it is updated <br>
8+
* <code>LIVELOAD</code> - read file each time an exchange happens
9+
*
10+
* @see FileHandler
11+
* @since 03.05.00
12+
* @version 03.05.00
13+
* @author Ktt Development
14+
*/
15+
public enum ByteLoadingOption {
16+
17+
PRELOAD,
18+
WATCHLOAD,
19+
LIVELOAD
20+
21+
}

src/main/java/com/kttdevelopment/simplehttpserver/handler/DirectoryEntry.java

Lines changed: 57 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,80 +3,60 @@
33
import java.io.*;
44
import java.nio.file.Files;
55
import java.nio.file.Path;
6-
import java.util.HashMap;
7-
import java.util.Objects;
6+
import java.util.*;
7+
import java.util.concurrent.ConcurrentHashMap;
88

99
/**
1010
* Represents a directory in the {@link FileHandler}. Applications do not use this class.
1111
*
1212
* @see FileHandler
1313
* @see FileEntry
1414
* @since 02.00.00
15-
* @version 02.00.00
15+
* @version 03.05.00
1616
* @author Ktt Development
1717
*/
1818
class DirectoryEntry {
1919

20-
private final boolean isWalkthrough;
21-
private final boolean isFilesPreloaded;
22-
2320
private final File directory;
24-
private final HashMap<String,FileEntry> files = new HashMap<>(); // preload only
25-
2621
private final FileHandlerAdapter adapter;
22+
private final ByteLoadingOption loadingOption;
23+
private final boolean isWalkthrough;
2724

28-
/**
29-
* Creates a directory entry.
30-
*
31-
* @param directory directory to represent
32-
* @param isFilesPreloaded whether to read bytes now or at runtime
33-
* @param adapter how to process the file name for context and bytes for {@link #getBytes(String)}
34-
* @param isWalkthrough if directory should contain inner folders
35-
* @throws FileNotFoundException if file was not found or is not a directory
36-
* @throws IOException if file walk failed for initial directory
37-
*
38-
* @see FileHandlerAdapter
39-
* @since 02.00.00
40-
* @author Ktt Development
41-
*/
42-
DirectoryEntry(final File directory, final boolean isFilesPreloaded, final FileHandlerAdapter adapter, final boolean isWalkthrough) throws IOException{
43-
if(!directory.exists() || !directory.isDirectory())
44-
throw new FileNotFoundException("Directory at " + directory.getAbsolutePath() + " was not found");
25+
private final Map<String,FileEntry> files = new ConcurrentHashMap<>(); // preload/watchload only
4526

27+
28+
DirectoryEntry(final File directory, final FileHandlerAdapter adapter, final ByteLoadingOption loadingOption, final boolean isWalkthrough) throws IOException{
4629
this.directory = directory;
47-
this.isFilesPreloaded = isFilesPreloaded;
48-
this.isWalkthrough = isWalkthrough;
4930
this.adapter = adapter;
31+
this.loadingOption = loadingOption;
32+
this.isWalkthrough = isWalkthrough;
5033

51-
if(isFilesPreloaded){
52-
if(!isWalkthrough){
53-
final File[] listFiles = directory.listFiles();
54-
for(final File file : (listFiles == null) ? new File[0] : listFiles)
55-
if(!file.isDirectory())
56-
files.put(
57-
getContext(adapter.getName(file)),
58-
new FileEntry(file, true, adapter)
59-
);
60-
}else{
61-
final Path dirPath = directory.toPath();
34+
if(loadingOption == ByteLoadingOption.WATCHLOAD){
6235

63-
Files.walk(dirPath).filter(path -> path.toFile().isDirectory()).forEach(path -> {
64-
final File pathFile = path.toFile();
65-
final String rel = dirPath.relativize(path).toString();
36+
}else if(loadingOption == ByteLoadingOption.PRELOAD){
37+
if(!isWalkthrough){
38+
final File[] listFiles = Objects.requireNonNullElse(directory.listFiles(),new File[0]);
39+
for(final File file : listFiles)
40+
if(!file.isDirectory())
41+
files.put(
42+
getContext(adapter.getName(file)),
43+
new FileEntry(file,adapter,ByteLoadingOption.PRELOAD)
44+
);
45+
}else{
46+
final Path dirPath = directory.toPath();
6647

67-
final File[] listFiles = pathFile.listFiles();
68-
for(final File file : (listFiles == null) ? new File[0] : listFiles){
69-
try{
48+
Files.walk(dirPath).filter(path -> path.toFile().isDirectory()).forEach(path -> {
49+
final File p2f = path.toFile();
50+
final String rel = dirPath.relativize(path).toString();
51+
52+
final File[] listFiles = Objects.requireNonNullElse(p2f.listFiles(), new File[0]);
53+
for(final File file : listFiles)
7054
files.put(
7155
(rel.isEmpty() || rel.equals("/") || rel.equals("\\") ? "" : getContext(rel)) + getContext(adapter.getName(file)),
72-
new FileEntry(file, true, adapter)
56+
new FileEntry(file,adapter,loadingOption)
7357
);
74-
}catch(final FileNotFoundException ignored){
75-
// #listFiles assume that all files exist, so this exception should never occur unless the user modified the directory mid-read.
76-
}
77-
}
78-
});
79-
}
58+
});
59+
}
8060
}
8161
}
8262

@@ -102,8 +82,9 @@ public final boolean isWalkthrough(){
10282
* @since 02.00.00
10383
* @author Ktt Development
10484
*/
85+
@Deprecated
10586
public final boolean isFilesPreloaded(){
106-
return isFilesPreloaded;
87+
return loadingOption != ByteLoadingOption.LIVELOAD;
10788
}
10889

10990
//
@@ -132,7 +113,7 @@ public final File getDirectory(){
132113
* @since 02.00.00
133114
* @author Ktt Development
134115
*/
135-
public HashMap<String, FileEntry> getFiles(){
116+
public Map<String, FileEntry> getFiles(){
136117
return files;
137118
}
138119

@@ -149,7 +130,7 @@ public HashMap<String, FileEntry> getFiles(){
149130
*/
150131
public final File getFile(final String path){
151132
final String rel = getContext(path);
152-
if(isFilesPreloaded){
133+
if(loadingOption != ByteLoadingOption.LIVELOAD){
153134
String match = "";
154135
for(final String key : files.keySet())
155136
if(rel.startsWith(key) && key.startsWith(match))
@@ -188,7 +169,7 @@ public final File getFile(final String path){
188169
*/
189170
public final byte[] getBytes(final String path){
190171
final String rel = getContext(path);
191-
if(isFilesPreloaded){
172+
if(loadingOption != ByteLoadingOption.LIVELOAD){
192173
String match = "";
193174
for(final String key : files.keySet())
194175
if(rel.startsWith(key) && key.startsWith(match))
@@ -208,6 +189,19 @@ public final byte[] getBytes(final String path){
208189
}
209190
}
210191

192+
/**
193+
* Returns the file's byte loading option.
194+
*
195+
* @return file loading option
196+
*
197+
* @see ByteLoadingOption
198+
* @since 03.05.00
199+
* @author Ktt Development
200+
*/
201+
public final ByteLoadingOption getLoadingOption(){
202+
return loadingOption;
203+
}
204+
211205
//
212206

213207
private static String getContext(final String path){
@@ -220,18 +214,16 @@ private static String getContext(final String path){
220214
//
221215

222216

223-
@SuppressWarnings("StringBufferReplaceableByString")
224217
@Override
225218
public String toString(){
226-
final StringBuilder OUT = new StringBuilder();
227-
OUT.append("DirectoryEntry") .append("{");
228-
OUT.append("isWalkthrough") .append("=") .append(isWalkthrough) .append(", ");
229-
OUT.append("isFilePreloaded") .append("=") .append(isFilesPreloaded) .append(", ");
230-
OUT.append("directory") .append("=") .append(directory) .append(", ");
231-
OUT.append("(preloaded) files") .append("=") .append(files) .append(", ");
232-
OUT.append("adapter") .append("=") .append(adapter);
233-
OUT.append("}");
234-
return OUT.toString();
219+
return
220+
"DirectoryEntry" + '{' +
221+
"directory" + '=' + directory + ", " +
222+
"adapter" + '=' + adapter + ", " +
223+
"loadingOption" + '=' + loadingOption + ", " +
224+
"isWalkthrough" + '=' + isWalkthrough + ", " +
225+
"files" + '=' + files +
226+
'}';
235227
}
236228

237229
}
Lines changed: 86 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,101 @@
11
package com.kttdevelopment.simplehttpserver.handler;
22

33
import java.io.*;
4-
import java.nio.file.Files;
4+
import java.nio.file.*;
55
import java.util.Arrays;
66

77
/**
88
* Represent a file in the {@link FileHandler}. Applications do not use this class.
99
*
10+
* @see ByteLoadingOption
1011
* @see FileHandler
1112
* @since 02.00.00
12-
* @version 02.00.00
13+
* @version 03.05.00
1314
* @author Ktt Development
1415
*/
1516
class FileEntry {
1617

17-
private final boolean isPreloaded;
18-
1918
private final File file;
19+
@SuppressWarnings("FieldCanBeLocal")
20+
private final FileBytesAdapter adapter;
21+
private final ByteLoadingOption loadingOption;
22+
2023
private byte[] preloadedBytes;
2124

2225
/**
2326
* Creates a file entry.
2427
*
2528
* @param file file to represent
26-
* @param isPreloaded whether to read bytes now or at runtime
2729
* @param bytesAdapter how to process the bytes in {@link #getBytes()}
28-
* @throws FileNotFoundException if file was not found or is not a file
30+
* @param loadingOption how to handle the initial file loading
31+
* @throws IOException failure to start watch service ({@link ByteLoadingOption#WATCHLOAD} only).
2932
*
3033
* @see FileBytesAdapter
31-
* @since 02.00.00
34+
* @see ByteLoadingOption
35+
* @since 03.05.00
3236
* @author Ktt Development
3337
*/
34-
FileEntry(final File file, final boolean isPreloaded, final FileBytesAdapter bytesAdapter) throws FileNotFoundException{
35-
if(!file.exists() || file.isDirectory())
36-
throw new FileNotFoundException("File at " + file.getAbsoluteFile() + " was not found");
37-
38-
this.file = file;
39-
this.isPreloaded = isPreloaded;
40-
41-
if(isPreloaded)
42-
try{
43-
preloadedBytes = bytesAdapter.getBytes(file,Files.readAllBytes(file.toPath()));
44-
}catch(final IOException ignored){
45-
preloadedBytes = null;
46-
}
38+
@SuppressWarnings("JavaDoc")
39+
FileEntry(final File file, final FileBytesAdapter bytesAdapter, final ByteLoadingOption loadingOption){
40+
this(file,bytesAdapter,loadingOption,true);
4741
}
4842

49-
//
50-
5143
/**
52-
* Returns if the file was preloaded.
44+
* Creates a file entry.
5345
*
54-
* @return if file was preloaded
46+
* @param file file to represent
47+
* @param bytesAdapter how to process the bytes in {@link #getBytes()}
48+
* @param loadingOption how to handle the initial file loading
49+
* @param skipWatchService skip creating a watch service ({@link ByteLoadingOption#WATCHLOAD} only).
50+
* @throws IOException failure to start watch service ({@link ByteLoadingOption#WATCHLOAD} only).
5551
*
56-
* @since 02.00.00
52+
* @see FileBytesAdapter
53+
* @see ByteLoadingOption
54+
* @since 03.05.00
5755
* @author Ktt Development
5856
*/
59-
public final boolean isPreloaded(){
60-
return isPreloaded;
57+
@SuppressWarnings("JavaDoc")
58+
FileEntry(final File file, final FileBytesAdapter bytesAdapter, final ByteLoadingOption loadingOption, final boolean skipWatchService){
59+
this.file = file;
60+
this.loadingOption = loadingOption;
61+
this.adapter = bytesAdapter;
62+
63+
switch(loadingOption){
64+
case WATCHLOAD:
65+
if(!skipWatchService)
66+
try{
67+
final WatchService service = FileSystems.getDefault().newWatchService();
68+
final Path path = file.toPath();
69+
path.register(service,StandardWatchEventKinds.ENTRY_CREATE,StandardWatchEventKinds.ENTRY_DELETE,StandardWatchEventKinds.ENTRY_MODIFY);
70+
71+
new Thread(() -> {
72+
WatchKey key;
73+
try{
74+
while ((key = service.take()) != null) {
75+
for (WatchEvent<?> event : key.pollEvents()) {
76+
try{
77+
final Path target = (Path) event.context();
78+
try{
79+
if(Files.isSameFile(path, target))
80+
preloadedBytes = bytesAdapter.getBytes(file,Files.readAllBytes(path));
81+
}catch(final IOException ignored){ } // don't overwrite if corrupt
82+
}catch(final ClassCastException ignored){ }
83+
}
84+
key.reset();
85+
}
86+
}catch(final InterruptedException ignored){ }
87+
}).start();
88+
}catch(IOException e){
89+
throw new RuntimeException(e);
90+
}
91+
case PRELOAD:
92+
try{
93+
preloadedBytes = bytesAdapter.getBytes(file,Files.readAllBytes(file.toPath()));
94+
}catch(final Exception ignored){
95+
preloadedBytes = null;
96+
}
97+
}
98+
6199
}
62100

63101
//
@@ -84,7 +122,7 @@ public final File getFile(){
84122
* @author Ktt Development
85123
*/
86124
public final byte[] getBytes(){
87-
if(isPreloaded)
125+
if(loadingOption != ByteLoadingOption.LIVELOAD)
88126
return preloadedBytes; // adapter determined preloaded bytes
89127
else
90128
try{
@@ -94,19 +132,31 @@ public final byte[] getBytes(){
94132
}
95133
}
96134

135+
/**
136+
* Returns the file's byte loading option.
137+
*
138+
* @return file loading option
139+
*
140+
* @see ByteLoadingOption
141+
* @since 03.05.00
142+
* @author Ktt Development
143+
*/
144+
public final ByteLoadingOption getLoadingOption(){
145+
return loadingOption;
146+
}
147+
97148
//
98149

99150

100-
@SuppressWarnings("StringBufferReplaceableByString")
101151
@Override
102152
public String toString(){
103-
final StringBuilder OUT = new StringBuilder();
104-
OUT.append("FileEntry") .append("{");
105-
OUT.append("isPreloaded") .append("=") .append(isPreloaded) .append(", ");
106-
OUT.append("file") .append("=") .append(file.toString()) .append(", ");
107-
OUT.append("(preloaded) bytes") .append("=") .append(Arrays.toString(preloadedBytes)) .append(", ");
108-
OUT.append("}");
109-
return OUT.toString();
153+
return
154+
"FileEntry" + '{' +
155+
"file" + '=' + file + ", " +
156+
"adapter" + '=' + adapter + ", " +
157+
"loadingOption" + '=' + loadingOption + ", " +
158+
"preloadedBytes" + '=' + Arrays.toString(preloadedBytes) +
159+
'}';
110160
}
111161

112162
}

0 commit comments

Comments
 (0)