1 package org.daisy.pipeline.fileset;
2
3 import java.io.ByteArrayInputStream;
4 import java.io.FileInputStream;
5 import java.io.FileNotFoundException;
6 import java.io.FileOutputStream;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.io.OutputStream;
10 import java.net.URI;
11 import java.util.ArrayList;
12 import java.util.List;
13 import java.util.NoSuchElementException;
14 import java.util.Optional;
15
16 import javax.xml.namespace.QName;
17 import static javax.xml.stream.XMLStreamConstants.END_DOCUMENT;
18 import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
19 import static javax.xml.stream.XMLStreamConstants.START_DOCUMENT;
20 import static javax.xml.stream.XMLStreamConstants.START_ELEMENT;
21 import javax.xml.stream.XMLStreamException;
22 import javax.xml.stream.XMLStreamWriter;
23
24 import org.daisy.common.stax.BaseURIAwareXMLStreamReader;
25 import org.daisy.common.stax.BaseURIAwareXMLStreamWriter;
26 import static org.daisy.common.stax.XMLStreamWriterHelper.writeAttribute;
27 import static org.daisy.common.stax.XMLStreamWriterHelper.writeStartElement;
28 import static org.daisy.pipeline.file.FileUtils.normalizeURI;
29 import static org.daisy.pipeline.file.FileUtils.relativizeURI;
30
31 public class Fileset {
32 private Fileset() {}
33
34
35
36
37
38
39 public static class File {
40
41 public final URI href;
42 public final Optional<String> mediaType;
43
44 private final FileData data;
45
46
47
48
49 private File(FileData data, URI href, Optional<String> mediaType) {
50 this.data = data;
51 this.href = href;
52 this.mediaType = mediaType;
53 }
54
55
56
57
58
59
60
61 public static File load(URI from, Optional<String> mediaType) {
62 return load(from, from, mediaType);
63 }
64
65 public static File load(URI from, URI href, Optional<String> mediaType) {
66 return load(new java.io.File(from), href, mediaType);
67 }
68
69 public static File load(java.io.File from, Optional<String> mediaType) {
70 return load(from, from.toURI(), mediaType);
71 }
72
73 public static File load(java.io.File from, URI href, Optional<String> mediaType) {
74 return new File(new FileDataOnDisk(from), normalizeURI(href), mediaType);
75 }
76
77 public static File load(byte[] data, URI href, Optional<String> mediaType) {
78 return new File(new FileDataInMemory(data), normalizeURI(href), mediaType);
79 }
80
81 public InputStream read() throws IOException {
82 return data.read();
83 }
84
85 public File copy(URI href) {
86 return new File(data, normalizeURI(href), mediaType);
87 }
88
89
90
91
92
93
94
95 public File store() throws IOException {
96 java.io.File file; {
97 try {
98 file = new java.io.File(href);
99 } catch (IllegalArgumentException e) {
100 throw new IOException("href is not a file URI: " + href);
101 }
102 }
103 return new File(data.store(file), href, mediaType);
104 }
105 }
106
107 private static abstract class FileData {
108
109 public abstract InputStream read() throws IOException;
110
111 public FileData store(java.io.File destination) throws IOException {
112 try (OutputStream os = new FileOutputStream(destination)) {
113 try (InputStream is = read()) {
114 byte[] bucket = new byte[1024];
115 int bytesRead = 0;
116 while ((bytesRead = is.read(bucket, 0, bucket.length)) != -1) {
117 os.write(bucket, 0, bytesRead);
118 }
119 }
120 }
121 return new FileDataOnDisk(destination);
122 }
123 }
124
125 private static class FileDataOnDisk extends FileData {
126
127 private final java.io.File file;
128
129
130
131
132 private FileDataOnDisk(java.io.File file) {
133 if (!(file.isFile() && file.exists()))
134 throw new IllegalArgumentException(new FileNotFoundException("file does not exist: " + file));
135 this.file = file;
136 }
137
138 public InputStream read() throws IOException {
139 return new FileInputStream(file);
140 }
141
142 @Override
143 public FileData store(java.io.File destination) throws IOException {
144 if (destination.equals(file))
145 return this;
146 return super.store(destination);
147 }
148 }
149
150 private static class FileDataInMemory extends FileData {
151
152 private final byte[] data;
153
154
155
156
157 private FileDataInMemory(byte[] data) {
158 this.data = data;
159 }
160
161 public InputStream read() {
162 return new ByteArrayInputStream(data);
163 }
164 }
165
166 public static List<File> unmarshall(BaseURIAwareXMLStreamReader xml) throws XMLStreamException {
167 List<File> fileset = new ArrayList<>();
168 URI filesetBase = xml.getBaseURI();
169 int depth = 0;
170 document: while (true) {
171 try {
172 int event = xml.next();
173 switch (event) {
174 case START_DOCUMENT:
175 break;
176 case END_DOCUMENT:
177 break document;
178 case START_ELEMENT:
179 if (depth == 0 && XMLConstants.D_FILESET.equals(xml.getName())) {
180 for (int i = 0; i < xml.getAttributeCount(); i++)
181 if (XMLConstants.XML_BASE.equals(xml.getAttributeName(i))) {
182 filesetBase = filesetBase.resolve(xml.getAttributeValue(i));
183 break;
184 }
185 depth++;
186 break;
187 } else if (depth == 1 && XMLConstants.D_FILE.equals(xml.getName())) {
188 URI href = null;
189 URI originalHref = null;
190 String mediaType = null;
191 for (int i = 0; i < xml.getAttributeCount(); i++)
192 if (XMLConstants._HREF.equals(xml.getAttributeName(i)))
193 href = filesetBase.resolve(xml.getAttributeValue(i));
194 else if (XMLConstants._ORIGINAL_HREF.equals(xml.getAttributeName(i)))
195 originalHref = filesetBase.resolve(xml.getAttributeValue(i));
196 else if (XMLConstants._MEDIA_TYPE.equals(xml.getAttributeName(i)))
197 mediaType = xml.getAttributeValue(i);
198 if (href != null) {
199 if (originalHref != null)
200 fileset.add(File.load(originalHref, href, Optional.ofNullable(mediaType)));
201 else
202 fileset.add(File.load(href, Optional.ofNullable(mediaType)));
203 }
204 }
205 {
206 int d = depth + 1;
207 element: while (true) {
208 event = xml.next();
209 switch (event) {
210 case START_ELEMENT:
211 d++;
212 break;
213 case END_ELEMENT:
214 d--;
215 if (d == depth) break element;
216 default:
217 }
218 }
219 }
220 break;
221 case END_ELEMENT:
222 depth--;
223 break;
224 default:
225 }
226 } catch (NoSuchElementException e) {
227 break;
228 }
229 }
230 return fileset;
231 }
232
233
234
235
236
237
238 public static void marshall(XMLStreamWriter xml, URI xmlBase, List<File> fileset) throws XMLStreamException {
239 xml.writeStartDocument();
240 writeStartElement(xml, XMLConstants.D_FILESET);
241 if (xmlBase != null)
242 writeAttribute(xml, XMLConstants.XML_BASE, xmlBase.toASCIIString());
243 for (File file : fileset) {
244 writeStartElement(xml, XMLConstants.D_FILE);
245 writeAttribute(xml,
246 XMLConstants._HREF,
247 (xmlBase != null ? relativizeURI(file.href, xmlBase) : file.href).toASCIIString());
248 if (file.mediaType.isPresent())
249 writeAttribute(xml, XMLConstants._MEDIA_TYPE, file.mediaType.get());
250 if (file.data instanceof FileDataOnDisk)
251 writeAttribute(xml,
252 XMLConstants._ORIGINAL_HREF,
253 ((FileDataOnDisk)file.data).file.toURI().toASCIIString());
254 xml.writeEndElement();
255 }
256 xml.writeEndElement();
257 xml.writeEndDocument();
258 }
259
260 public static final class XMLConstants {
261 private XMLConstants() {}
262
263 public static final QName D_FILESET = new QName("http://www.daisy.org/ns/pipeline/data", "fileset", "d");
264 public static final QName XML_BASE = new QName(javax.xml.XMLConstants.XML_NS_URI, "base", "xml");
265 public static final QName D_FILE = new QName("http://www.daisy.org/ns/pipeline/data", "file", "d");
266 public static final QName _MEDIA_TYPE = new QName("media-type");
267 public static final QName _HREF = new QName("href");
268 public static final QName _ORIGINAL_HREF = new QName("original-href");
269 }
270 }