/
FileType.java
132 lines (114 loc) · 4.22 KB
/
FileType.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/*
* Copyright 2019 National Bank of Belgium
*
* Licensed under the EUPL, Version 1.1 or - as soon they will be approved
* by the European Commission - subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and
* limitations under the Licence.
*/
package nbbrd.service.examples;
import internal.nbbrd.service.examples.FileTypeSpiLoader;
import nbbrd.service.Quantifier;
import nbbrd.service.ServiceDefinition;
import nbbrd.service.ServiceProvider;
import nbbrd.service.ServiceSorter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Locale;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* @author Philippe Charles
*/
public final class FileType {
private FileType() {
// static class
}
// 💡 API: designed to be called and used
public static Optional<String> probeContentType(Path file) throws IOException {
for (FileTypeSpi probe : FileTypeSpiLoader.get()) {
String result = probe.getContentTypeOrNull(file);
if (result != null) return Optional.of(result);
}
return Optional.empty();
}
public static void main(String[] args) throws IOException {
for (String file : Arrays.asList("hello.csv", "stuff.txt")) {
System.out.println(file + ": " + FileType.probeContentType(Paths.get(file)).orElse("?"));
}
}
// 💡 SPI: designed to be extended and implemented
@ServiceDefinition(
quantifier = Quantifier.MULTIPLE,
loaderName = "internal.{{canonicalName}}Loader",
singleton = true
)
public interface FileTypeSpi {
enum Accuracy {HIGH, LOW}
String getContentTypeOrNull(Path file) throws IOException;
@ServiceSorter
Accuracy getAccuracy();
}
@ServiceProvider
public static final class ByExtensionProbe implements FileTypeSpi {
@Override
public String getContentTypeOrNull(Path file) {
switch (getExtension(file)) {
case "csv":
return "text/csv";
case "avi":
return "video/x-msvideo";
default:
return null;
}
}
@Override
public Accuracy getAccuracy() {
return Accuracy.LOW;
}
private String getExtension(Path file) {
int index = file.toString().lastIndexOf('.');
return index != -1 ? file.toString().substring(index + 1).toLowerCase(Locale.ROOT) : "";
}
}
@ServiceProvider
public static final class ByMagicNumberProbe implements FileTypeSpi {
@Override
public String getContentTypeOrNull(Path file) throws IOException {
String magicId = getMagicIdAsString(file, 8);
if (magicId.startsWith("D0CF11E0A1B11AE1")) return "application/vnd.ms-excel";
if (magicId.startsWith("255044462D")) return "application/pdf";
return null;
}
@Override
public Accuracy getAccuracy() {
return Accuracy.HIGH;
}
private String getMagicIdAsString(Path file, int maxBytes) throws IOException {
if (Files.isReadable(file)) {
try (InputStream stream = Files.newInputStream(file)) {
byte[] bytes = new byte[maxBytes];
int n = stream.read(bytes);
return IntStream.range(0, n)
.mapToObj(i -> String.format(Locale.ROOT, "%02x", bytes[i]))
.collect(Collectors.joining())
.toUpperCase(Locale.ROOT);
}
}
return "";
}
}
}