aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/fasterxml/jackson/databind/deser/DataFormatReaders.java
blob: 7bd78fb69b9c45cdd4f46eb0b149f15d40c602a4 (plain)
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
package com.fasterxml.jackson.databind.deser;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.format.*;
import com.fasterxml.jackson.core.io.MergedStream;

import com.fasterxml.jackson.databind.*;

/**
 * Alternative to {@link DataFormatDetector} that needs to be used when
 * using data-binding.
 * 
 * @since 2.1
 */
public class DataFormatReaders
{
    /**
     * By default we will look ahead at most 64 bytes; in most cases,
     * much less (4 bytes or so) is needed, but we will allow bit more
     * leniency to support data formats that need more complex heuristics.
     */
    public final static int DEFAULT_MAX_INPUT_LOOKAHEAD = 64;
    
    /**
     * Ordered list of readers which both represent data formats to
     * detect (in precedence order, starting with highest) and contain
     * factories used for actual detection.
     */
    protected final ObjectReader[] _readers;

    /**
     * Strength of match we consider to be good enough to be used
     * without checking any other formats.
     * Default value is {@link MatchStrength#SOLID_MATCH}, 
     */
    protected final MatchStrength _optimalMatch;

    /**
     * Strength of minimal match we accept as the answer, unless
     * better matches are found. 
     * Default value is {@link MatchStrength#WEAK_MATCH}, 
     */
    protected final MatchStrength _minimalMatch;

    /**
     * Maximum number of leading bytes of the input that we can read
     * to determine data format.
     *<p>
     * Default value is {@link #DEFAULT_MAX_INPUT_LOOKAHEAD}.
     */
    protected final int _maxInputLookahead;
    
    /*
    /**********************************************************
    /* Construction
    /**********************************************************
     */
    
    public DataFormatReaders(ObjectReader... detectors) {
        this(detectors, MatchStrength.SOLID_MATCH, MatchStrength.WEAK_MATCH,
            DEFAULT_MAX_INPUT_LOOKAHEAD);
    }

    public DataFormatReaders(Collection<ObjectReader> detectors) {
        this(detectors.toArray(new ObjectReader[detectors.size()]));
    }

    private DataFormatReaders(ObjectReader[] detectors,
            MatchStrength optMatch, MatchStrength minMatch,
            int maxInputLookahead)
    {
        _readers = detectors;
        _optimalMatch = optMatch;
        _minimalMatch = minMatch;
        _maxInputLookahead = maxInputLookahead;
    }
    
    /*
    /**********************************************************
    /* Fluent factories for changing match settings
    /**********************************************************
     */
    
    public DataFormatReaders withOptimalMatch(MatchStrength optMatch) {
        if (optMatch == _optimalMatch) {
            return this;
        }
        return new DataFormatReaders(_readers, optMatch, _minimalMatch, _maxInputLookahead);
    }

    public DataFormatReaders withMinimalMatch(MatchStrength minMatch) {
        if (minMatch == _minimalMatch) {
            return this;
        }
        return new DataFormatReaders(_readers, _optimalMatch, minMatch, _maxInputLookahead);
    }

    public DataFormatReaders with(ObjectReader[] readers) {
        return new DataFormatReaders(readers, _optimalMatch, _minimalMatch, _maxInputLookahead);
    }
    
    public DataFormatReaders withMaxInputLookahead(int lookaheadBytes)
    {
        if (lookaheadBytes == _maxInputLookahead) {
            return this;
        }
        return new DataFormatReaders(_readers, _optimalMatch, _minimalMatch, lookaheadBytes);
    }

    /*
    /**********************************************************
    /* Fluent factories for changing underlying readers
    /**********************************************************
     */

    public DataFormatReaders with(DeserializationConfig config)
    {
        final int len = _readers.length;
        ObjectReader[] r = new ObjectReader[len];
        for (int i = 0; i < len; ++i) {
            r[i] = _readers[i].with(config);
        }
        return new DataFormatReaders(r, _optimalMatch, _minimalMatch, _maxInputLookahead);
    }

    public DataFormatReaders withType(JavaType type)
    {
        final int len = _readers.length;
        ObjectReader[] r = new ObjectReader[len];
        for (int i = 0; i < len; ++i) {
            r[i] = _readers[i].forType(type);
        }
        return new DataFormatReaders(r, _optimalMatch, _minimalMatch, _maxInputLookahead);
    }
    
    /*
    /**********************************************************
    /* Public API
    /**********************************************************
     */

    /**
     * Method to call to find format that content (accessible via given
     * {@link InputStream}) given has, as per configuration of this detector
     * instance.
     * 
     * @return Matcher object which contains result; never null, even in cases
     *    where no match (with specified minimal match strength) is found.
     */
    public Match findFormat(InputStream in) throws IOException
    {
        return _findFormat(new AccessorForReader(in, new byte[_maxInputLookahead]));
    }

    /**
     * Method to call to find format that given content (full document)
     * has, as per configuration of this detector instance.
     * 
     * @return Matcher object which contains result; never null, even in cases
     *    where no match (with specified minimal match strength) is found.
     */
    public Match findFormat(byte[] fullInputData) throws IOException
    {
        return _findFormat(new AccessorForReader(fullInputData));
    }

    /**
     * Method to call to find format that given content (full document)
     * has, as per configuration of this detector instance.
     * 
     * @return Matcher object which contains result; never null, even in cases
     *    where no match (with specified minimal match strength) is found.
     * 
     * @since 2.1
     */
    public Match findFormat(byte[] fullInputData, int offset, int len) throws IOException
    {
        return _findFormat(new AccessorForReader(fullInputData, offset, len));
    }
    
    /*
    /**********************************************************
    /* Overrides
    /**********************************************************
     */

    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        final int len = _readers.length;
        if (len > 0) {
            sb.append(_readers[0].getFactory().getFormatName());
            for (int i = 1; i < len; ++i) {
                sb.append(", ");
                sb.append(_readers[i].getFactory().getFormatName());
            }
        }
        sb.append(']');
        return sb.toString();
    }
    
    /*
    /**********************************************************
    /* Internal methods
    /**********************************************************
     */

    private Match _findFormat(AccessorForReader acc) throws IOException
    {
        ObjectReader bestMatch = null;
        MatchStrength bestMatchStrength = null;
        for (ObjectReader f : _readers) {
            acc.reset();
            MatchStrength strength = f.getFactory().hasFormat(acc);
            // if not better than what we have so far (including minimal level limit), skip
            if (strength == null || strength.ordinal() < _minimalMatch.ordinal()) {
                continue;
            }
            // also, needs to better match than before
            if (bestMatch != null) {
                if (bestMatchStrength.ordinal() >= strength.ordinal()) {
                    continue;
                }
            }
            // finally: if it's good enough match, we are done
            bestMatch = f;
            bestMatchStrength = strength;
            if (strength.ordinal() >= _optimalMatch.ordinal()) {
                break;
            }
        }
        return acc.createMatcher(bestMatch, bestMatchStrength);
    }

    /*
    /**********************************************************
    /* Helper classes
    /**********************************************************
     */

    /**
     * We need sub-class here as well, to be able to access efficiently.
     */
    protected class AccessorForReader extends InputAccessor.Std
    {
        public AccessorForReader(InputStream in, byte[] buffer) {
            super(in, buffer);
        }
        public AccessorForReader(byte[] inputDocument) {
            super(inputDocument);
        }
        public AccessorForReader(byte[] inputDocument, int start, int len) {
            super(inputDocument, start, len);
        }

        public Match createMatcher(ObjectReader match, MatchStrength matchStrength)
        {
            return new Match(_in, _buffer, _bufferedStart, (_bufferedEnd - _bufferedStart),
                    match, matchStrength);
        }
    }
    
    /**
     * Result class, similar to {@link DataFormatMatcher}
     */
    public static class Match
    {
        protected final InputStream _originalStream;

        /**
         * Content read during format matching process
         */
        protected final byte[] _bufferedData;

        /**
         * Pointer to the first byte in buffer available for reading
         */
        protected final int _bufferedStart;
        
        /**
         * Number of bytes available in buffer.
         */
        protected final int _bufferedLength;

        /**
         * Factory that produced sufficient match (if any)
         */
        protected final ObjectReader _match;

        /**
         * Strength of match with {@link #_match}
         */
        protected final MatchStrength _matchStrength;
        
        protected Match(InputStream in, byte[] buffered,
                int bufferedStart, int bufferedLength,
                ObjectReader match, MatchStrength strength)
        {
            _originalStream = in;
            _bufferedData = buffered;
            _bufferedStart = bufferedStart;
            _bufferedLength = bufferedLength;
            _match = match;
            _matchStrength = strength;
        }

        /*
        /**********************************************************
        /* Public API, simple accessors
        /**********************************************************
         */

        /**
         * Accessor to use to see if any formats matched well enough with
         * the input data.
         */
        public boolean hasMatch() { return _match != null; }

        /**
         * Method for accessing strength of the match, if any; if no match,
         * will return {@link MatchStrength#INCONCLUSIVE}.
         */
        public MatchStrength getMatchStrength() {
            return (_matchStrength == null) ? MatchStrength.INCONCLUSIVE : _matchStrength;
        }

        /**
         * Accessor for {@link JsonFactory} that represents format that data matched.
         */
        public ObjectReader getReader() { return _match; }

        /**
         * Accessor for getting brief textual name of matched format if any (null
         * if none). Equivalent to:
         *<pre>
         *   return hasMatch() ? getMatch().getFormatName() : null;
         *</pre>
         */
        public String getMatchedFormatName() {
            return _match.getFactory().getFormatName();
        }
        
        /*
        /**********************************************************
        /* Public API, factory methods
        /**********************************************************
         */
        
        /**
         * Convenience method for trying to construct a {@link JsonParser} for
         * parsing content which is assumed to be in detected data format.
         * If no match was found, returns null.
         */
        public JsonParser createParserWithMatch() throws IOException
        {
            if (_match == null) {
                return null;
            }
            JsonFactory jf = _match.getFactory();
            if (_originalStream == null) {
                return jf.createParser(_bufferedData, _bufferedStart, _bufferedLength);
            }
            return jf.createParser(getDataStream());
        }
        
        /**
         * Method to use for accessing input for which format detection has been done.
         * This <b>must</b> be used instead of using stream passed to detector
         * unless given stream itself can do buffering.
         * Stream will return all content that was read during matching process, as well
         * as remaining contents of the underlying stream.
         */
        public InputStream getDataStream() {
            if (_originalStream == null) {
                return new ByteArrayInputStream(_bufferedData, _bufferedStart, _bufferedLength);
            }
            return new MergedStream(null, _originalStream, _bufferedData, _bufferedStart, _bufferedLength);
        }        
    }
    
}