source: FCSSimpleEndpoint/trunk/src/main/java/eu/clarin/sru/server/fcs/AdvancedDataViewWriter.java @ 7273

Last change on this file since 7273 was 7273, checked in by Oliver Schonefeld, 2 years ago
  • update copyright
  • Property svn:eol-style set to native
File size: 12.4 KB
Line 
1/**
2 * This software is copyright (c) 2013-2022 by
3 *  - Leibniz-Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
4 * This is free software. You can redistribute it
5 * and/or modify it under the terms described in
6 * the GNU General Public License v3 of which you
7 * should have received a copy. Otherwise you can download
8 * it from
9 *
10 *   http://www.gnu.org/licenses/gpl-3.0.txt
11 *
12 * @copyright Leibniz-Institut fuer Deutsche Sprache (http://www.ids-mannheim.de)
13 *
14 * @license http://www.gnu.org/licenses/gpl-3.0.txt
15 *  GNU General Public License v3
16 */
17package eu.clarin.sru.server.fcs;
18
19import java.net.URI;
20import java.util.ArrayList;
21import java.util.HashMap;
22import java.util.List;
23import java.util.Map;
24
25import javax.xml.stream.XMLStreamException;
26import javax.xml.stream.XMLStreamWriter;
27
28
29/**
30 * Helper class for serializing Advanced Data Views. It can be used for writing
31 * more than once, but it is <em>not thread-save</em>. This helper can also
32 * serialize HITS Data Views.
33 */
34public class AdvancedDataViewWriter {
35    public enum Unit {
36        ITEM, TIMESTAMP
37    }
38    private static final long INITIAL_SEGMENT_ID = 1;
39    public static final int NO_HIGHLIGHT = -1;
40    private static final String ADV_PREFIX = "adv";
41    private static final String ADV_NS =
42            "http://clarin.eu/fcs/dataview/advanced";
43    private static final String ADV_MIME_TYPE =
44            "application/x-clarin-fcs-adv+xml";
45    private static final String HITS_MIME_TYPE =
46            "application/x-clarin-fcs-hits+xml";
47    private static final String FCS_HITS_PREFIX = "hits";
48    private static final String FCS_HITS_NS =
49            "http://clarin.eu/fcs/dataview/hits";
50    private final Unit unit;
51    private final List<Segment> segments = new ArrayList<Segment>();
52    private final Map<URI, List<Span>> layers = new HashMap<URI, List<Span>>();
53    private long nextSegmentId = INITIAL_SEGMENT_ID;
54
55
56    /**
57     * Constructor.
58     *
59     * @param unit
60     *            the unit to be used for span offsets
61     * @see Unit
62     */
63    public AdvancedDataViewWriter(Unit unit) {
64        if (unit == null) {
65            throw new NullPointerException("unit == null");
66        }
67        this.unit = unit;
68    }
69
70
71    /**
72     * Reset the writer for writing a new data view (instance).
73     */
74    public void reset() {
75        nextSegmentId = INITIAL_SEGMENT_ID;
76    }
77
78
79    /**
80     * Add a span.
81     *
82     * @param layerId
83     *            the span's layer id
84     * @param start
85     *            the span's start offset
86     * @param end
87     *            the span's end offset
88     * @param value
89     *            the span's content value or <code>null</code> if none
90     * @throws IllegalArgumentException
91     *             if any argument is invalid
92     */
93    public void addSpan(URI layerId, long start, long end, String value) {
94        addSpan(layerId, start, end, value, null, NO_HIGHLIGHT);
95    }
96
97
98    /**
99     * Add a span.
100     *
101     * @param layerId
102     *            the span's layer id
103     * @param start
104     *            the span's start offset
105     * @param end
106     *            the span's end offset
107     * @param value
108     *            the span's content value or <code>null</code> if none
109     * @param highlight
110     *            the highlight group
111     * @throws IllegalArgumentException
112     *             if any argument is invalid
113     */
114    public void addSpan(URI layerId, long start, long end, String value,
115            int highlight) {
116        addSpan(layerId, start, end, value, null, highlight);
117    }
118
119
120    /**
121     * Add a span.
122     *
123     * @param layerId
124     *            the span's layer id
125     * @param start
126     *            the span's start offset
127     * @param end
128     *            the span's end offset
129     * @param value
130     *            the span's content value or <code>null</code> if none
131     * @param altValue
132     *            the span's alternate value or <code>null</code> if none
133     */
134    public void addSpan(URI layerId, long start, long end, String value,
135            String altValue) {
136        addSpan(layerId, start, end, value, altValue, NO_HIGHLIGHT);
137    }
138
139
140    /**
141     * Add a span.
142     *
143     * @param layerId
144     *            the span's layer id
145     * @param start
146     *            the span's start offset
147     * @param end
148     *            the span's end offset
149     * @param value
150     *            the span's content value or <code>null</code> if none
151     * @param altValue
152     *            the span's alternate value or <code>null</code> if none
153     * @param highlight
154     *            the span's alternate value or <code>null</code> if none
155     * @param highlight
156     *            the highlight group
157     * @throws IllegalArgumentException
158     *             if any argument is invalid
159     */
160    public void addSpan(URI layerId, long start, long end, String value,
161            String altValue, int highlight) {
162        if (layerId == null) {
163            throw new NullPointerException("layerId == null");
164        }
165        if (start < 0) {
166            throw new IllegalArgumentException("start < 0");
167        }
168        if (end < start) {
169            throw new IllegalArgumentException("end < start");
170        }
171        if (highlight <= 0) {
172            highlight = NO_HIGHLIGHT;
173        }
174
175        // find segment or create a new one
176        Segment segment = null;
177        for (Segment seg : segments) {
178            if ((seg.start == start) && (seg.end == end)) {
179                segment = seg;
180                break;
181            }
182        }
183        if (segment == null) {
184            segment = new Segment(nextSegmentId++, start, end);
185            segments.add(segment);
186        }
187
188        // find layer or create a new one
189        List<Span> layer = layers.get(layerId);
190        if (layer == null) {
191            layer = new ArrayList<Span>();
192            layers.put(layerId, layer);
193        }
194
195        // sanity check (better overlap check?)
196        for (Span span : layer) {
197            if (segment.equals(span.segment)) {
198                // FIXME: better exception!
199                throw new IllegalArgumentException(
200                        "segment already exists in layer");
201            }
202        }
203        layer.add(new Span(segment, value, altValue, highlight));
204    }
205
206
207    /**
208     * Write the Advanced Data View to the output stream.
209     *
210     * @param writer
211     *            the writer to write to
212     * @throws XMLStreamException
213     *             if an error occurred
214     */
215    public void writeAdvancedDataView(XMLStreamWriter writer)
216            throws XMLStreamException {
217        if (writer == null) {
218            throw new NullPointerException("writer == null");
219        }
220
221        XMLStreamWriterHelper.writeStartDataView(writer, ADV_MIME_TYPE);
222        writer.setPrefix(ADV_PREFIX, ADV_NS);
223        writer.writeStartElement(ADV_NS, "Advanced");
224        writer.writeNamespace(ADV_PREFIX, ADV_NS);
225        if (unit == Unit.ITEM) {
226            writer.writeAttribute("unit", "item");
227        } else if (unit == Unit.TIMESTAMP) {
228            writer.writeAttribute("unit", "timestamp");
229        }
230
231        // segments
232        writer.writeStartElement(ADV_NS, "Segments");
233        for (Segment segment : segments) {
234            // FIXME: unit translation (long -> time)
235            writer.writeEmptyElement(ADV_NS, "Segment");
236            writer.writeAttribute("id", segment.id);
237            writer.writeAttribute("start", Long.toString(segment.start));
238            writer.writeAttribute("end", Long.toString(segment.end));
239            if (segment.ref != null) {
240                writer.writeAttribute("ref", segment.ref.toString());
241            }
242        }
243        writer.writeEndElement(); // "Segments" element
244
245        // layers
246        writer.writeStartElement(ADV_NS, "Layers");
247        for (Map.Entry<URI, List<Span>> layer : layers.entrySet()) {
248            writer.writeStartElement(ADV_NS, "Layer");
249            writer.writeAttribute("id", layer.getKey().toString());
250            for (Span span : layer.getValue()) {
251                if ((span.value != null) && !span.value.isEmpty()) {
252                    writer.writeStartElement(ADV_NS, "Span");
253                    writer.writeAttribute("ref", span.segment.id);
254                    if (span.highlight != null) {
255                        writer.writeAttribute("highlight", span.highlight);
256                    }
257                    if (span.altValue != null) {
258                        writer.writeAttribute("alt-value", span.altValue);
259                    }
260                    writer.writeCharacters(span.value);
261                    writer.writeEndElement(); // "Span" element
262                } else {
263                    writer.writeEmptyElement(ADV_NS, "Span");
264                    writer.writeAttribute("ref", span.segment.id);
265                    if (span.highlight != null) {
266                        writer.writeAttribute("highlight", span.highlight);
267                    }
268                    if (span.altValue != null) {
269                        writer.writeAttribute("alt-value", span.altValue);
270                    }
271                }
272            }
273            writer.writeEndElement(); // "Layer" element
274        }
275        writer.writeEndElement(); // "Layers" element
276
277        writer.writeEndElement(); // "Advanced" element
278        XMLStreamWriterHelper.writeEndDataView(writer);
279    }
280
281
282    /**
283     * Convenience method to write HITS Data View.
284     *
285     * @param writer
286     *            the writer to write to
287     * @param layerId
288     *            the layer id of the layer to be serialized as HITS Data View
289     * @throws XMLStreamException
290     *             if an error occurred
291     * @throws IllegalArgumentException
292     *             if an invalid layer id was provided
293     */
294    public void writeHitsDataView(XMLStreamWriter writer, URI layerId)
295            throws XMLStreamException {
296        if (writer == null) {
297            throw new NullPointerException("writer == null");
298        }
299        if (layerId == null) {
300            throw new NullPointerException("layerId == null");
301        }
302
303        final List<Span> spans = layers.get(layerId);
304        if (spans == null) {
305            throw new IllegalArgumentException(
306                    "layer with id'" + layerId + "' does not exist");
307        }
308        XMLStreamWriterHelper.writeStartDataView(writer, HITS_MIME_TYPE);
309        writer.setPrefix(FCS_HITS_PREFIX, FCS_HITS_NS);
310        writer.writeStartElement(FCS_HITS_NS, "Result");
311        writer.writeNamespace(FCS_HITS_PREFIX, FCS_HITS_NS);
312        boolean needSpace = false;
313        for (Span span : spans) {
314            if (span.value.length() > 0) {
315                if (needSpace) {
316                    writer.writeCharacters(" ");
317                    needSpace = false;
318                }
319                if (span.highlight != null) {
320                    writer.writeStartElement(FCS_HITS_NS, "Hit");
321                    writer.writeCharacters(span.value);
322                    writer.writeEndElement(); // "Hit" element
323                    needSpace = true;
324                } else {
325                    writer.writeCharacters(span.value);
326                    if (!Character.isWhitespace(
327                            (span.value.charAt(span.value.length() - 1)))) {
328                        needSpace = true;
329                    }
330                }
331            }
332        }
333        writer.writeEndElement(); // "Result" element
334        XMLStreamWriterHelper.writeEndDataView(writer);
335    }
336
337    private static final class Segment {
338        private final String id;
339        private final long start;
340        private final long end;
341        private final URI ref;
342
343
344        private Segment(long id, long start, long end) {
345            this.id = "s" + Long.toHexString(id);
346            this.start = start;
347            this.end = end;
348            /*
349             * FIXME: add API to set reference
350             */
351            this.ref = null;
352        }
353    }
354
355    private static final class Span {
356        private final Segment segment;
357        private final String value;
358        private final String altValue;
359        private final String highlight;
360
361
362        private Span(Segment segment, String value, String altValue,
363                int highlight) {
364            this.segment = segment;
365            this.value = value;
366            this.altValue = altValue;
367            if (highlight != NO_HIGHLIGHT) {
368                this.highlight = "h" + Integer.toHexString(highlight);
369            } else {
370                this.highlight = null;
371            }
372        }
373    }
374
375} // class AdvancedDataViewWriter
Note: See TracBrowser for help on using the repository browser.