EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.value]

COVERAGE SUMMARY FOR SOURCE FILE [ValueLob.java]

nameclass, %method, %block, %line, %
ValueLob.java100% (1/1)0%   (0/49)0%   (0/1604)0%   (0/365)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ValueLob100% (1/1)0%   (0/49)0%   (0/1604)0%   (0/365)
ValueLob (int, DataHandler, String, int, int, boolean, long, boolean): void 0%   (0/1)0%   (0/27)0%   (0/10)
ValueLob (int, byte []): void 0%   (0/1)0%   (0/26)0%   (0/8)
close (): void 0%   (0/1)0%   (0/18)0%   (0/6)
compareSecure (Value, CompareMode): int 0%   (0/1)0%   (0/19)0%   (0/4)
convertPrecision (long, boolean): Value 0%   (0/1)0%   (0/28)0%   (0/6)
convertTo (int): Value 0%   (0/1)0%   (0/34)0%   (0/9)
convertToFileIfRequired (DataHandler): void 0%   (0/1)0%   (0/68)0%   (0/14)
copy (ValueLob): ValueLob 0%   (0/1)0%   (0/30)0%   (0/4)
copyFileTo (DataHandler, String, String): void 0%   (0/1)0%   (0/23)0%   (0/7)
copyToTemp (): ValueLob 0%   (0/1)0%   (0/23)0%   (0/4)
createBlob (InputStream, long, DataHandler): ValueLob 0%   (0/1)0%   (0/92)0%   (0/22)
createClob (Reader, long, DataHandler): ValueLob 0%   (0/1)0%   (0/97)0%   (0/23)
createFromReader (char [], int, Reader, long, DataHandler): void 0%   (0/1)0%   (0/67)0%   (0/16)
createFromStream (byte [], int, InputStream, long, DataHandler): void 0%   (0/1)0%   (0/56)0%   (0/14)
createSmallLob (int, byte []): ValueLob 0%   (0/1)0%   (0/6)0%   (0/1)
deleteFile (DataHandler, String): void 0%   (0/1)0%   (0/16)0%   (0/4)
equals (Object): boolean 0%   (0/1)0%   (0/13)0%   (0/1)
getBufferSize (DataHandler, boolean, long): int 0%   (0/1)0%   (0/56)0%   (0/12)
getBytes (): byte [] 0%   (0/1)0%   (0/13)0%   (0/4)
getBytesNoCopy (): byte [] 0%   (0/1)0%   (0/24)0%   (0/7)
getDisplaySize (): int 0%   (0/1)0%   (0/4)0%   (0/1)
getFileList (DataHandler, String): String [] 0%   (0/1)0%   (0/46)0%   (0/10)
getFileName (): String 0%   (0/1)0%   (0/3)0%   (0/1)
getFileName (DataHandler, int, int): String 0%   (0/1)0%   (0/36)0%   (0/4)
getFileNamePrefix (String, int): String 0%   (0/1)0%   (0/60)0%   (0/11)
getInputStream (): InputStream 0%   (0/1)0%   (0/33)0%   (0/5)
getMemory (): int 0%   (0/1)0%   (0/11)0%   (0/3)
getNewObjectId (DataHandler): int 0%   (0/1)0%   (0/149)0%   (0/38)
getObject (): Object 0%   (0/1)0%   (0/10)0%   (0/3)
getObjectId (): int 0%   (0/1)0%   (0/3)0%   (0/1)
getPrecision (): long 0%   (0/1)0%   (0/3)0%   (0/1)
getReader (): Reader 0%   (0/1)0%   (0/4)0%   (0/1)
getSQL (): String 0%   (0/1)0%   (0/27)0%   (0/6)
getSmall (): byte [] 0%   (0/1)0%   (0/3)0%   (0/1)
getString (): String 0%   (0/1)0%   (0/56)0%   (0/11)
getTableId (): int 0%   (0/1)0%   (0/3)0%   (0/1)
getTraceSQL (): String 0%   (0/1)0%   (0/48)0%   (0/8)
getType (): int 0%   (0/1)0%   (0/3)0%   (0/1)
hashCode (): int 0%   (0/1)0%   (0/35)0%   (0/7)
initLarge (DataHandler): FileStoreOutputStream 0%   (0/1)0%   (0/97)0%   (0/19)
invalidateFileList (DataHandler, String): void 0%   (0/1)0%   (0/22)0%   (0/6)
isCompressed (): boolean 0%   (0/1)0%   (0/3)0%   (0/1)
isLinked (): boolean 0%   (0/1)0%   (0/3)0%   (0/1)
link (DataHandler, int): Value 0%   (0/1)0%   (0/76)0%   (0/22)
openLinked (int, DataHandler, int, int, long, boolean): ValueLob 0%   (0/1)0%   (0/17)0%   (0/2)
openUnlinked (int, DataHandler, int, int, long, boolean, String): ValueLob 0%   (0/1)0%   (0/12)0%   (0/1)
renameFile (DataHandler, String, String): void 0%   (0/1)0%   (0/17)0%   (0/4)
set (PreparedStatement, int): void 0%   (0/1)0%   (0/33)0%   (0/7)
unlink (DataHandler): void 0%   (0/1)0%   (0/51)0%   (0/12)

1/*
2 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
3 * and the EPL 1.0 (http://h2database.com/html/license.html).
4 * Initial Developer: H2 Group
5 */
6package org.h2.value;
7 
8import java.io.BufferedInputStream;
9import java.io.ByteArrayInputStream;
10import java.io.File;
11import java.io.IOException;
12import java.io.InputStream;
13import java.io.Reader;
14import java.sql.PreparedStatement;
15import java.sql.SQLException;
16 
17import org.h2.engine.Constants;
18import org.h2.engine.SysProperties;
19import org.h2.message.DbException;
20import org.h2.mvstore.DataUtils;
21import org.h2.store.DataHandler;
22import org.h2.store.FileStore;
23import org.h2.store.FileStoreInputStream;
24import org.h2.store.FileStoreOutputStream;
25import org.h2.store.fs.FileUtils;
26import org.h2.util.IOUtils;
27import org.h2.util.MathUtils;
28import org.h2.util.SmallLRUCache;
29import org.h2.util.StringUtils;
30import org.h2.util.Utils;
31 
32/**
33 * Implementation of the BLOB and CLOB data types. Small objects are kept in
34 * memory and stored in the record.
35 *
36 * Large objects are stored in their own files. When large objects are set in a
37 * prepared statement, they are first stored as 'temporary' files. Later, when
38 * they are used in a record, and when the record is stored, the lob files are
39 * linked: the file is renamed using the file format (tableId).(objectId). There
40 * is one exception: large variables are stored in the file (-1).(objectId).
41 *
42 * When lobs are deleted, they are first renamed to a temp file, and if the
43 * delete operation is committed the file is deleted.
44 *
45 * Data compression is supported.
46 */
47public class ValueLob extends Value {
48 
49    /**
50     * This counter is used to calculate the next directory to store lobs. It is
51     * better than using a random number because less directories are created.
52     */
53    private static int dirCounter;
54 
55    private final int type;
56    private long precision;
57    private DataHandler handler;
58    private int tableId;
59    private int objectId;
60    private String fileName;
61    private boolean linked;
62    private byte[] small;
63    private int hash;
64    private boolean compressed;
65    private FileStore tempFile;
66 
67    private ValueLob(int type, DataHandler handler, String fileName,
68            int tableId, int objectId, boolean linked, long precision,
69            boolean compressed) {
70        this.type = type;
71        this.handler = handler;
72        this.fileName = fileName;
73        this.tableId = tableId;
74        this.objectId = objectId;
75        this.linked = linked;
76        this.precision = precision;
77        this.compressed = compressed;
78    }
79 
80    private ValueLob(int type, byte[] small) {
81        this.type = type;
82        this.small = small;
83        if (small != null) {
84            if (type == Value.BLOB) {
85                this.precision = small.length;
86            } else {
87                this.precision = getString().length();
88            }
89        }
90    }
91 
92    private static ValueLob copy(ValueLob lob) {
93        ValueLob copy = new ValueLob(lob.type, lob.handler, lob.fileName,
94                lob.tableId, lob.objectId, lob.linked, lob.precision, lob.compressed);
95        copy.small = lob.small;
96        copy.hash = lob.hash;
97        return copy;
98    }
99 
100    /**
101     * Create a small lob using the given byte array.
102     *
103     * @param type the type (Value.BLOB or CLOB)
104     * @param small the byte array
105     * @return the lob value
106     */
107    private static ValueLob createSmallLob(int type, byte[] small) {
108        return new ValueLob(type, small);
109    }
110 
111    private static String getFileName(DataHandler handler, int tableId,
112            int objectId) {
113        if (SysProperties.CHECK && tableId == 0 && objectId == 0) {
114            DbException.throwInternalError("0 LOB");
115        }
116        String table = tableId < 0 ? ".temp" : ".t" + tableId;
117        return getFileNamePrefix(handler.getDatabasePath(), objectId) +
118                table + Constants.SUFFIX_LOB_FILE;
119    }
120 
121    /**
122     * Create a LOB value with the given parameters.
123     *
124     * @param type the data type
125     * @param handler the file handler
126     * @param tableId the table object id
127     * @param objectId the object id
128     * @param precision the precision (length in elements)
129     * @param compression if compression is used
130     * @return the value object
131     */
132    public static ValueLob openLinked(int type, DataHandler handler,
133            int tableId, int objectId, long precision, boolean compression) {
134        String fileName = getFileName(handler, tableId, objectId);
135        return new ValueLob(type, handler, fileName, tableId, objectId,
136                true/* linked */, precision, compression);
137    }
138 
139    /**
140     * Create a LOB value with the given parameters.
141     *
142     * @param type the data type
143     * @param handler the file handler
144     * @param tableId the table object id
145     * @param objectId the object id
146     * @param precision the precision (length in elements)
147     * @param compression if compression is used
148     * @param fileName the file name
149     * @return the value object
150     */
151    public static ValueLob openUnlinked(int type, DataHandler handler,
152            int tableId, int objectId, long precision, boolean compression,
153            String fileName) {
154        return new ValueLob(type, handler, fileName, tableId, objectId,
155                false/* linked */, precision, compression);
156    }
157 
158    /**
159     * Create a CLOB value from a stream.
160     *
161     * @param in the reader
162     * @param length the number of characters to read, or -1 for no limit
163     * @param handler the data handler
164     * @return the lob value
165     */
166    private static ValueLob createClob(Reader in, long length,
167            DataHandler handler) {
168        try {
169            if (handler == null) {
170                String s = IOUtils.readStringAndClose(in, (int) length);
171                return createSmallLob(Value.CLOB, s.getBytes(Constants.UTF8));
172            }
173            boolean compress = handler.getLobCompressionAlgorithm(Value.CLOB) != null;
174            long remaining = Long.MAX_VALUE;
175            if (length >= 0 && length < remaining) {
176                remaining = length;
177            }
178            int len = getBufferSize(handler, compress, remaining);
179            char[] buff;
180            if (len >= Integer.MAX_VALUE) {
181                String data = IOUtils.readStringAndClose(in, -1);
182                buff = data.toCharArray();
183                len = buff.length;
184            } else {
185                buff = new char[len];
186                len = IOUtils.readFully(in, buff, len);
187            }
188            if (len <= handler.getMaxLengthInplaceLob()) {
189                byte[] small = new String(buff, 0, len).getBytes(Constants.UTF8);
190                return ValueLob.createSmallLob(Value.CLOB, small);
191            }
192            ValueLob lob = new ValueLob(Value.CLOB, null);
193            lob.createFromReader(buff, len, in, remaining, handler);
194            return lob;
195        } catch (IOException e) {
196            throw DbException.convertIOException(e, null);
197        }
198    }
199 
200    private static int getBufferSize(DataHandler handler, boolean compress,
201            long remaining) {
202        if (remaining < 0 || remaining > Integer.MAX_VALUE) {
203            remaining = Integer.MAX_VALUE;
204        }
205        int inplace = handler.getMaxLengthInplaceLob();
206        long m = compress ?
207                Constants.IO_BUFFER_SIZE_COMPRESS : Constants.IO_BUFFER_SIZE;
208        if (m < remaining && m <= inplace) {
209            // using "1L" to force long arithmetic
210            m = Math.min(remaining, inplace + 1L);
211            // the buffer size must be bigger than the inplace lob, otherwise we
212            // can't know if it must be stored in-place or not
213            m = MathUtils.roundUpLong(m, Constants.IO_BUFFER_SIZE);
214        }
215        m = Math.min(remaining, m);
216        m = MathUtils.convertLongToInt(m);
217        if (m < 0) {
218            m = Integer.MAX_VALUE;
219        }
220        return (int) m;
221    }
222 
223    private void createFromReader(char[] buff, int len, Reader in,
224            long remaining, DataHandler h) throws IOException {
225        FileStoreOutputStream out = initLarge(h);
226        boolean compress = h.getLobCompressionAlgorithm(Value.CLOB) != null;
227        try {
228            while (true) {
229                precision += len;
230                byte[] b = new String(buff, 0, len).getBytes(Constants.UTF8);
231                out.write(b, 0, b.length);
232                remaining -= len;
233                if (remaining <= 0) {
234                    break;
235                }
236                len = getBufferSize(h, compress, remaining);
237                len = IOUtils.readFully(in, buff, len);
238                if (len == 0) {
239                    break;
240                }
241            }
242        } finally {
243            out.close();
244        }
245    }
246 
247    private static String getFileNamePrefix(String path, int objectId) {
248        String name;
249        int f = objectId % SysProperties.LOB_FILES_PER_DIRECTORY;
250        if (f > 0) {
251            name = SysProperties.FILE_SEPARATOR + objectId;
252        } else {
253            name = "";
254        }
255        objectId /= SysProperties.LOB_FILES_PER_DIRECTORY;
256        while (objectId > 0) {
257            f = objectId % SysProperties.LOB_FILES_PER_DIRECTORY;
258            name = SysProperties.FILE_SEPARATOR + f +
259                    Constants.SUFFIX_LOBS_DIRECTORY + name;
260            objectId /= SysProperties.LOB_FILES_PER_DIRECTORY;
261        }
262        name = FileUtils.toRealPath(path +
263                Constants.SUFFIX_LOBS_DIRECTORY + name);
264        return name;
265    }
266 
267    private static int getNewObjectId(DataHandler h) {
268        String path = h.getDatabasePath();
269        if ((path != null) && (path.length() == 0)) {
270            path = new File(Utils.getProperty("java.io.tmpdir", "."),
271                    SysProperties.PREFIX_TEMP_FILE).getAbsolutePath();
272        }
273        int newId = 0;
274        int lobsPerDir = SysProperties.LOB_FILES_PER_DIRECTORY;
275        while (true) {
276            String dir = getFileNamePrefix(path, newId);
277            String[] list = getFileList(h, dir);
278            int fileCount = 0;
279            boolean[] used = new boolean[lobsPerDir];
280            for (String name : list) {
281                if (name.endsWith(Constants.SUFFIX_DB_FILE)) {
282                    name = FileUtils.getName(name);
283                    String n = name.substring(0, name.indexOf('.'));
284                    int id;
285                    try {
286                        id = Integer.parseInt(n);
287                    } catch (NumberFormatException e) {
288                        id = -1;
289                    }
290                    if (id > 0) {
291                        fileCount++;
292                        used[id % lobsPerDir] = true;
293                    }
294                }
295            }
296            int fileId = -1;
297            if (fileCount < lobsPerDir) {
298                for (int i = 1; i < lobsPerDir; i++) {
299                    if (!used[i]) {
300                        fileId = i;
301                        break;
302                    }
303                }
304            }
305            if (fileId > 0) {
306                newId += fileId;
307                invalidateFileList(h, dir);
308                break;
309            }
310            if (newId > Integer.MAX_VALUE / lobsPerDir) {
311                // this directory path is full: start from zero
312                newId = 0;
313                dirCounter = MathUtils.randomInt(lobsPerDir - 1) * lobsPerDir;
314            } else {
315                // calculate the directory.
316                // start with 1 (otherwise we don't know the number of
317                // directories).
318                // it doesn't really matter what directory is used, it might as
319                // well be random (but that would generate more directories):
320                // int dirId = RandomUtils.nextInt(lobsPerDir - 1) + 1;
321                int dirId = (dirCounter++ / (lobsPerDir - 1)) + 1;
322                newId = newId * lobsPerDir;
323                newId += dirId * lobsPerDir;
324            }
325        }
326        return newId;
327    }
328 
329    private static void invalidateFileList(DataHandler h, String dir) {
330        SmallLRUCache<String, String[]> cache = h.getLobFileListCache();
331        if (cache != null) {
332            synchronized (cache) {
333                cache.remove(dir);
334            }
335        }
336    }
337 
338    private static String[] getFileList(DataHandler h, String dir) {
339        SmallLRUCache<String, String[]> cache = h.getLobFileListCache();
340        String[] list;
341        if (cache == null) {
342            list = FileUtils.newDirectoryStream(dir).toArray(new String[0]);
343        } else {
344            synchronized (cache) {
345                list = cache.get(dir);
346                if (list == null) {
347                    list = FileUtils.newDirectoryStream(dir).toArray(new String[0]);
348                    cache.put(dir, list);
349                }
350            }
351        }
352        return list;
353    }
354 
355    /**
356     * Create a BLOB value from a stream.
357     *
358     * @param in the input stream
359     * @param length the number of characters to read, or -1 for no limit
360     * @param handler the data handler
361     * @return the lob value
362     */
363    private static ValueLob createBlob(InputStream in, long length,
364            DataHandler handler) {
365        try {
366            if (handler == null) {
367                byte[] data = IOUtils.readBytesAndClose(in, (int) length);
368                return createSmallLob(Value.BLOB, data);
369            }
370            long remaining = Long.MAX_VALUE;
371            boolean compress = handler.getLobCompressionAlgorithm(Value.BLOB) != null;
372            if (length >= 0 && length < remaining) {
373                remaining = length;
374            }
375            int len = getBufferSize(handler, compress, remaining);
376            byte[] buff;
377            if (len >= Integer.MAX_VALUE) {
378                buff = IOUtils.readBytesAndClose(in, -1);
379                len = buff.length;
380            } else {
381                buff = DataUtils.newBytes(len);
382                len = IOUtils.readFully(in, buff, len);
383            }
384            if (len <= handler.getMaxLengthInplaceLob()) {
385                byte[] small = DataUtils.newBytes(len);
386                System.arraycopy(buff, 0, small, 0, len);
387                return ValueLob.createSmallLob(Value.BLOB, small);
388            }
389            ValueLob lob = new ValueLob(Value.BLOB, null);
390            lob.createFromStream(buff, len, in, remaining, handler);
391            return lob;
392        } catch (IOException e) {
393            throw DbException.convertIOException(e, null);
394        }
395    }
396 
397    private FileStoreOutputStream initLarge(DataHandler h) {
398        this.handler = h;
399        this.tableId = 0;
400        this.linked = false;
401        this.precision = 0;
402        this.small = null;
403        this.hash = 0;
404        String compressionAlgorithm = h.getLobCompressionAlgorithm(type);
405        this.compressed = compressionAlgorithm != null;
406        synchronized (h) {
407            String path = h.getDatabasePath();
408            if ((path != null) && (path.length() == 0)) {
409                path = new File(Utils.getProperty("java.io.tmpdir", "."),
410                        SysProperties.PREFIX_TEMP_FILE).getAbsolutePath();
411            }
412            objectId = getNewObjectId(h);
413            fileName = getFileNamePrefix(path, objectId) + Constants.SUFFIX_TEMP_FILE;
414            tempFile = h.openFile(fileName, "rw", false);
415            tempFile.autoDelete();
416        }
417        FileStoreOutputStream out = new FileStoreOutputStream(tempFile, h,
418                compressionAlgorithm);
419        return out;
420    }
421 
422    private void createFromStream(byte[] buff, int len, InputStream in,
423            long remaining, DataHandler h) throws IOException {
424        FileStoreOutputStream out = initLarge(h);
425        boolean compress = h.getLobCompressionAlgorithm(Value.BLOB) != null;
426        try {
427            while (true) {
428                precision += len;
429                out.write(buff, 0, len);
430                remaining -= len;
431                if (remaining <= 0) {
432                    break;
433                }
434                len = getBufferSize(h, compress, remaining);
435                len = IOUtils.readFully(in, buff, len);
436                if (len <= 0) {
437                    break;
438                }
439            }
440        } finally {
441            out.close();
442        }
443    }
444 
445    /**
446     * Convert a lob to another data type. The data is fully read in memory
447     * except when converting to BLOB or CLOB.
448     *
449     * @param t the new type
450     * @return the converted value
451     */
452    @Override
453    public Value convertTo(int t) {
454        if (t == type) {
455            return this;
456        } else if (t == Value.CLOB) {
457            ValueLob copy = ValueLob.createClob(getReader(), -1, handler);
458            return copy;
459        } else if (t == Value.BLOB) {
460            ValueLob copy = ValueLob.createBlob(getInputStream(), -1, handler);
461            return copy;
462        }
463        return super.convertTo(t);
464    }
465 
466    @Override
467    public boolean isLinked() {
468        return linked;
469    }
470 
471    /**
472     * Get the current file name where the lob is saved.
473     *
474     * @return the file name or null
475     */
476    public String getFileName() {
477        return fileName;
478    }
479 
480    @Override
481    public void close() {
482        if (fileName != null) {
483            if (tempFile != null) {
484                tempFile.stopAutoDelete();
485                tempFile = null;
486            }
487            deleteFile(handler, fileName);
488        }
489    }
490 
491    @Override
492    public void unlink(DataHandler handler) {
493        if (linked && fileName != null) {
494            String temp;
495            // synchronize on the database, to avoid concurrent temp file
496            // creation / deletion / backup
497            synchronized (handler) {
498                temp = getFileName(handler, -1, objectId);
499                deleteFile(handler, temp);
500                renameFile(handler, fileName, temp);
501                tempFile = FileStore.open(handler, temp, "rw");
502                tempFile.autoDelete();
503                tempFile.closeSilently();
504                fileName = temp;
505                linked = false;
506            }
507        }
508    }
509 
510    @Override
511    public Value link(DataHandler h, int tabId) {
512        if (fileName == null) {
513            this.tableId = tabId;
514            return this;
515        }
516        if (linked) {
517            ValueLob copy = ValueLob.copy(this);
518            copy.objectId = getNewObjectId(h);
519            copy.tableId = tabId;
520            String live = getFileName(h, copy.tableId, copy.objectId);
521            copyFileTo(h, fileName, live);
522            copy.fileName = live;
523            copy.linked = true;
524            return copy;
525        }
526        if (!linked) {
527            this.tableId = tabId;
528            String live = getFileName(h, tableId, objectId);
529            if (tempFile != null) {
530                tempFile.stopAutoDelete();
531                tempFile = null;
532            }
533            renameFile(h, fileName, live);
534            fileName = live;
535            linked = true;
536        }
537        return this;
538    }
539 
540    /**
541     * Get the current table id of this lob.
542     *
543     * @return the table id
544     */
545    @Override
546    public int getTableId() {
547        return tableId;
548    }
549 
550    /**
551     * Get the current object id of this lob.
552     *
553     * @return the object id
554     */
555    public int getObjectId() {
556        return objectId;
557    }
558 
559    @Override
560    public int getType() {
561        return type;
562    }
563 
564    @Override
565    public long getPrecision() {
566        return precision;
567    }
568 
569    @Override
570    public String getString() {
571        int len = precision > Integer.MAX_VALUE || precision == 0 ?
572                Integer.MAX_VALUE : (int) precision;
573        try {
574            if (type == Value.CLOB) {
575                if (small != null) {
576                    return new String(small, Constants.UTF8);
577                }
578                return IOUtils.readStringAndClose(getReader(), len);
579            }
580            byte[] buff;
581            if (small != null) {
582                buff = small;
583            } else {
584                buff = IOUtils.readBytesAndClose(getInputStream(), len);
585            }
586            return StringUtils.convertBytesToHex(buff);
587        } catch (IOException e) {
588            throw DbException.convertIOException(e, fileName);
589        }
590    }
591 
592    @Override
593    public byte[] getBytes() {
594        if (type == CLOB) {
595            // convert hex to string
596            return super.getBytes();
597        }
598        byte[] data = getBytesNoCopy();
599        return Utils.cloneByteArray(data);
600    }
601 
602    @Override
603    public byte[] getBytesNoCopy() {
604        if (type == CLOB) {
605            // convert hex to string
606            return super.getBytesNoCopy();
607        }
608        if (small != null) {
609            return small;
610        }
611        try {
612            return IOUtils.readBytesAndClose(
613                    getInputStream(), Integer.MAX_VALUE);
614        } catch (IOException e) {
615            throw DbException.convertIOException(e, fileName);
616        }
617    }
618 
619    @Override
620    public int hashCode() {
621        if (hash == 0) {
622            if (precision > 4096) {
623                // TODO: should calculate the hash code when saving, and store
624                // it in the database file
625                return (int) (precision ^ (precision >>> 32));
626            }
627            if (type == CLOB) {
628                hash = getString().hashCode();
629            } else {
630                hash = Utils.getByteArrayHash(getBytes());
631            }
632        }
633        return hash;
634    }
635 
636    @Override
637    protected int compareSecure(Value v, CompareMode mode) {
638        if (type == Value.CLOB) {
639            return Integer.signum(getString().compareTo(v.getString()));
640        }
641        byte[] v2 = v.getBytesNoCopy();
642        return Utils.compareNotNullSigned(getBytes(), v2);
643    }
644 
645    @Override
646    public Object getObject() {
647        if (type == Value.CLOB) {
648            return getReader();
649        }
650        return getInputStream();
651    }
652 
653    @Override
654    public Reader getReader() {
655        return IOUtils.getBufferedReader(getInputStream());
656    }
657 
658    @Override
659    public InputStream getInputStream() {
660        if (fileName == null) {
661            return new ByteArrayInputStream(small);
662        }
663        FileStore store = handler.openFile(fileName, "r", true);
664        boolean alwaysClose = SysProperties.lobCloseBetweenReads;
665        return new BufferedInputStream(
666                new FileStoreInputStream(store, handler, compressed, alwaysClose),
667                Constants.IO_BUFFER_SIZE);
668    }
669 
670    @Override
671    public void set(PreparedStatement prep, int parameterIndex)
672            throws SQLException {
673        long p = getPrecision();
674        if (p > Integer.MAX_VALUE || p <= 0) {
675            p = -1;
676        }
677        if (type == Value.BLOB) {
678            prep.setBinaryStream(parameterIndex, getInputStream(), (int) p);
679        } else {
680            prep.setCharacterStream(parameterIndex, getReader(), (int) p);
681        }
682    }
683 
684    @Override
685    public String getSQL() {
686        String s;
687        if (type == Value.CLOB) {
688            s = getString();
689            return StringUtils.quoteStringSQL(s);
690        }
691        byte[] buff = getBytes();
692        s = StringUtils.convertBytesToHex(buff);
693        return "X'" + s + "'";
694    }
695 
696    @Override
697    public String getTraceSQL() {
698        if (small != null && getPrecision() <= SysProperties.MAX_TRACE_DATA_LENGTH) {
699            return getSQL();
700        }
701        StringBuilder buff = new StringBuilder();
702        if (type == Value.CLOB) {
703            buff.append("SPACE(").append(getPrecision());
704        } else {
705            buff.append("CAST(REPEAT('00', ").append(getPrecision()).append(") AS BINARY");
706        }
707        buff.append(" /* ").append(fileName).append(" */)");
708        return buff.toString();
709    }
710 
711    /**
712     * Get the data if this a small lob value.
713     *
714     * @return the data
715     */
716    @Override
717    public byte[] getSmall() {
718        return small;
719    }
720 
721    @Override
722    public int getDisplaySize() {
723        return MathUtils.convertLongToInt(getPrecision());
724    }
725 
726    @Override
727    public boolean equals(Object other) {
728        return other instanceof ValueLob && compareSecure((Value) other, null) == 0;
729    }
730 
731    /**
732     * Store the lob data to a file if the size of the buffer is larger than the
733     * maximum size for an in-place lob.
734     *
735     * @param h the data handler
736     */
737    public void convertToFileIfRequired(DataHandler h) {
738        try {
739            if (small != null && small.length > h.getMaxLengthInplaceLob()) {
740                boolean compress = h.getLobCompressionAlgorithm(type) != null;
741                int len = getBufferSize(h, compress, Long.MAX_VALUE);
742                int tabId = tableId;
743                if (type == Value.BLOB) {
744                    createFromStream(
745                            DataUtils.newBytes(len), 0, getInputStream(), Long.MAX_VALUE, h);
746                } else {
747                    createFromReader(
748                            new char[len], 0, getReader(), Long.MAX_VALUE, h);
749                }
750                Value v2 = link(h, tabId);
751                if (SysProperties.CHECK && v2 != this) {
752                    DbException.throwInternalError();
753                }
754            }
755        } catch (IOException e) {
756            throw DbException.convertIOException(e, null);
757        }
758    }
759 
760    /**
761     * Check if this lob value is compressed.
762     *
763     * @return true if it is
764     */
765    public boolean isCompressed() {
766        return compressed;
767    }
768 
769    private static synchronized void deleteFile(DataHandler handler,
770            String fileName) {
771        // synchronize on the database, to avoid concurrent temp file creation /
772        // deletion / backup
773        synchronized (handler.getLobSyncObject()) {
774            FileUtils.delete(fileName);
775        }
776    }
777 
778    private static synchronized void renameFile(DataHandler handler,
779            String oldName, String newName) {
780        synchronized (handler.getLobSyncObject()) {
781            FileUtils.move(oldName, newName);
782        }
783    }
784 
785    private static void copyFileTo(DataHandler h, String sourceFileName,
786            String targetFileName) {
787        synchronized (h.getLobSyncObject()) {
788            try {
789                IOUtils.copyFiles(sourceFileName, targetFileName);
790            } catch (IOException e) {
791                throw DbException.convertIOException(e, null);
792            }
793        }
794    }
795 
796    @Override
797    public int getMemory() {
798        if (small != null) {
799            return small.length + 104;
800        }
801        return 140;
802    }
803 
804    /**
805     * Create an independent copy of this temporary value.
806     * The file will not be deleted automatically.
807     *
808     * @return the value
809     */
810    @Override
811    public ValueLob copyToTemp() {
812        ValueLob lob;
813        if (type == CLOB) {
814            lob = ValueLob.createClob(getReader(), precision, handler);
815        } else {
816            lob = ValueLob.createBlob(getInputStream(), precision, handler);
817        }
818        return lob;
819    }
820 
821    @Override
822    public Value convertPrecision(long precision, boolean force) {
823        if (this.precision <= precision) {
824            return this;
825        }
826        ValueLob lob;
827        if (type == CLOB) {
828            lob = ValueLob.createClob(getReader(), precision, handler);
829        } else {
830            lob = ValueLob.createBlob(getInputStream(), precision, handler);
831        }
832        return lob;
833    }
834 
835}

[all classes][org.h2.value]
EMMA 2.0.5312 (C) Vladimir Roubtsov