-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathTransparencyBuffer.java
More file actions
132 lines (130 loc) · 4.93 KB
/
TransparencyBuffer.java
File metadata and controls
132 lines (130 loc) · 4.93 KB
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
// Part of SourceAFIS Transparency API: https://sourceafis.machinezoo.com/transparency/
package com.machinezoo.sourceafis.transparency;
import java.io.*;
import java.util.*;
import java.util.function.*;
import java.util.regex.*;
import java.util.zip.*;
import org.apache.commons.io.*;
import com.machinezoo.noexception.*;
import com.machinezoo.sourceafis.*;
import com.machinezoo.stagean.*;
public class TransparencyBuffer {
private final Map<TransparencyKey<?>, List<TransparencyRecord<?>>> map = new HashMap<>();
private TransparencyFilter filter = TransparencyFilter.any();
/*
* Intentionally applied only to future additions, leaving existing records unaffected,
* so that different filters can be applied to different sources of transparency data.
*/
public TransparencyBuffer accept(TransparencyFilter filter) {
this.filter = filter != null ? filter : TransparencyFilter.any();
return this;
}
public boolean accepts(TransparencyKey<?> key) {
Objects.requireNonNull(key);
return filter.accepts(key);
}
public TransparencyBuffer add(TransparencyRecord<?> record) {
Objects.requireNonNull(record);
if (accepts(record.key()))
map.computeIfAbsent(record.key(), k -> new ArrayList<>()).add(record);
return this;
}
public TransparencyBuffer write(TransparencyKey<?> key, String mime, byte[] data) {
return add(new TransparencyRecord<>(key, mime, data));
}
public TransparencyBuffer write(TransparencyKey<?> key, byte[] data) {
return write(key, key.mime(), data);
}
public TransparencyBuffer writeLazy(TransparencyKey<?> key, String mime, Supplier<byte[]> supplier) {
if (accepts(key))
write(key, mime, supplier.get());
return this;
}
public TransparencyBuffer writeLazy(TransparencyKey<?> key, Supplier<byte[]> supplier) {
return writeLazy(key, key.mime(), supplier);
}
public <T> TransparencyBuffer serialize(TransparencyKey<T> key, String mime, T object) {
return write(key, mime, key.serialize(mime, object));
}
public <T> TransparencyBuffer serialize(TransparencyKey<T> key, T object) {
return serialize(key, key.mime(), object);
}
public <T> TransparencyBuffer serializeLazy(TransparencyKey<T> key, String mime, Supplier<T> supplier) {
if (accepts(key))
serialize(key, mime, supplier.get());
return this;
}
public <T> TransparencyBuffer serializeLazy(TransparencyKey<T> key, Supplier<T> supplier) {
return serializeLazy(key, key.mime(), supplier);
}
public TransparencyBuffer append(Collection<TransparencyRecord<?>> records) {
for (var record : records)
add(record);
return this;
}
public TransparencyBuffer append(TransparencyArchive archive) {
return append(archive.toList());
}
public boolean accepts(String key) {
return filter.accepts(key);
}
@DraftCode("Support for multiple versions of SourceAFIS and multiple MIME types will require smarter key parsing.")
public TransparencyBuffer take(String key, String mime, byte[] data) {
return write(TransparencyKey.parse(key), mime, data);
}
public FingerprintTransparency open() {
return new TransparencyBufferLogger(this);
}
@DraftApi("Prevents capture of outputs. Complement with method taking Consumer<TransparencyBuffer> or implement other means to capture operation output.")
public TransparencyBuffer capture(Runnable action) {
try (var transparency = open()) {
action.run();
}
return this;
}
private static final Pattern FILENAME_RE = Pattern.compile("^[0-9]+-([a-z-]+)(\\.[a-z]+)$");
@DraftCode("Use proper MIME library.")
private static String mime(String suffix) {
return switch (suffix) {
case ".cbor" -> "application/cbor";
case ".txt" -> "text/plain";
case ".json" -> "application/json";
case ".xml" -> "application/xml";
case ".jpeg" -> "image/jpeg";
case ".png" -> "image/png";
case ".bmp" -> "image/bmp";
case ".tiff" -> "image/tiff";
case ".jp2" -> "image/jp2";
case ".wsq" -> "image/x-wsq";
default -> "application/octet-stream";
};
}
@DraftCode("Find better solution for checked exceptions.")
@DraftApi("Support ZipFile, which is more efficient when filters are applied.")
public TransparencyBuffer unzip(InputStream stream) {
Exceptions.wrap().run(() -> {
try (var zip = new ZipInputStream(stream)) {
while (true) {
var entry = zip.getNextEntry();
if (entry == null)
break;
var matcher = FILENAME_RE.matcher(entry.getName());
/*
* Silently ignore unrecognized files, which could have been automatically generated by OS or various apps.
*/
if (matcher.matches())
TransparencyBuffer.this.take(matcher.group(1), mime(matcher.group(2)), IOUtils.toByteArray(zip));
}
}
});
return this;
}
@DraftApi("Poor naming. Choose single word name, ideally a verb.")
public TransparencyArchive toArchive() {
var immutable = new HashMap<TransparencyKey<?>, List<TransparencyRecord<?>>>();
for (var key : map.keySet())
immutable.put(key, Collections.unmodifiableList(new ArrayList<>(map.get(key))));
return new PlainTransparencyArchive(immutable);
}
}