KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > derby > iapi > types > ReaderToUTF8Stream


1 /*
2
3    Derby - Class org.apache.derby.iapi.types.ReaderToUTF8Stream
4
5    Licensed to the Apache Software Foundation (ASF) under one or more
6    contributor license agreements. See the NOTICE file distributed with
7    this work for additional information regarding copyright ownership.
8    The ASF licenses this file to you under the Apache License, Version 2.0
9    (the "License"); you may not use this file except in compliance with
10    the License. You may obtain a copy of the License at
11
12       http://www.apache.org/licenses/LICENSE-2.0
13
14    Unless required by applicable law or agreed to in writing, software
15    distributed under the License is distributed on an "AS IS" BASIS,
16    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17    See the License for the specific language governing permissions and
18    limitations under the License.
19
20  */

21
22 package org.apache.derby.iapi.types;
23
24 import java.io.InputStream JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.io.EOFException JavaDoc;
27 import java.io.Reader JavaDoc;
28 import java.io.UTFDataFormatException JavaDoc;
29 import org.apache.derby.iapi.reference.SQLState;
30 import org.apache.derby.iapi.services.i18n.MessageService;
31 import org.apache.derby.iapi.services.io.DerbyIOException;
32 import org.apache.derby.iapi.services.io.LimitReader;
33 import org.apache.derby.iapi.types.TypeId;
34
35 /**
36     Converts a java.io.Reader to the on-disk UTF8 format used by Derby
37     for character types.
38 */

39 public final class ReaderToUTF8Stream
40     extends InputStream JavaDoc
41 {
42     /**
43      * Application's reader wrapped in a LimitReader.
44      */

45     private LimitReader reader;
46
47     private byte[] buffer;
48     private int boff;
49     private int blen;
50     private boolean eof;
51     private boolean multipleBuffer;
52     // buffer to hold the data read from stream
53
// and converted to UTF8 format
54
private final static int BUFSIZE = 32768;
55     
56     /** Number of characters to truncate from this stream
57      The SQL standard allows for truncation of trailing spaces
58      for clobs,varchar,char.
59      If zero, no characters are truncated.
60      */

61     private final int charsToTruncate;
62     private static final char SPACE = ' ';
63     
64     /**
65      * Length of the final value, after truncation if any,
66      * in characters.
67      this stream needs to fit into a column of colWidth
68      if truncation error happens ,then the error message includes
69      information about the column width.
70     */

71     private final int valueLength;
72     /** The maximum allowed length of the stream. */
73     private final int maximumLength;
74     /** The type name for the column data is inserted into. */
75     private final String JavaDoc typeName;
76     
77     /**
78      * Create a stream that will truncate trailing blanks if required/allowed.
79      *
80      * If the stream must be truncated, the number of blanks to truncate
81      * is specified to allow the stream to be checked for exact length, as
82      * required by JDBC 3.0. If the stream is shorter or longer than specified,
83      * an exception is thrown during read.
84      *
85      * @param appReader application reader
86      * @param valueLength the length of the reader in characters
87      * @param numCharsToTruncate the number of trailing blanks to truncate
88      * @param typeName type name of the column data is inserted into
89      */

90     public ReaderToUTF8Stream(Reader JavaDoc appReader,
91                               int valueLength,
92                               int numCharsToTruncate,
93                               String JavaDoc typeName) {
94         this.reader = new LimitReader(appReader);
95         reader.setLimit(valueLength);
96         buffer = new byte[BUFSIZE];
97         blen = -1;
98         this.charsToTruncate = numCharsToTruncate;
99         this.valueLength = valueLength;
100         this.maximumLength = -1;
101         this.typeName = typeName;
102     }
103
104     /**
105      * Create a UTF-8 stream for a length less application reader.
106      *
107      * A limit is placed on the length of the reader. If the reader exceeds
108      * the maximum length, truncation of trailing blanks is attempted. If
109      * truncation fails, an exception is thrown.
110      *
111      * @param appReader application reader
112      * @param maximumLength maximum allowed length in number of characters for
113      * the reader
114      * @param typeName type name of the column data is inserted into
115      * @throws IllegalArgumentException if maximum length is negative, or type
116      * name is <code>null<code>
117      */

118     public ReaderToUTF8Stream(Reader JavaDoc appReader,
119                               int maximumLength,
120                               String JavaDoc typeName) {
121         if (maximumLength < 0) {
122             throw new IllegalArgumentException JavaDoc("Maximum length for a capped " +
123                     "stream cannot be negative: " + maximumLength);
124         }
125         if (typeName == null) {
126             throw new IllegalArgumentException JavaDoc("Type name cannot be null");
127         }
128         this.reader = new LimitReader(appReader);
129         reader.setLimit(maximumLength);
130         buffer = new byte[BUFSIZE];
131         blen = -1;
132         this.maximumLength = maximumLength;
133         this.typeName = typeName;
134         this.charsToTruncate = -1;
135         this.valueLength = -1;
136     }
137
138     /**
139      * read from stream; characters converted to utf-8 derby specific encoding.
140      * If stream has been read, and eof reached, in that case any subsequent
141      * read will throw an EOFException
142      * @see java.io.InputStream#read()
143      */

144     public int read() throws IOException JavaDoc {
145
146         // when stream has been read and eof reached, stream is closed
147
// and buffer is set to null ( see close() method)
148
// since stream cannot be re-used, check if stream is closed and
149
// if so throw an EOFException
150
if ( buffer == null)
151             throw new EOFException JavaDoc(MessageService.getTextMessage(SQLState.STREAM_EOF));
152
153         
154         // first read
155
if (blen < 0)
156             fillBuffer(2);
157
158         while (boff == blen)
159         {
160             // reached end of buffer, read more?
161
if (eof)
162             {
163                // we have reached the end of this stream
164
// cleanup here and return -1 indicating
165
// eof of stream
166
close();
167                return -1;
168             }
169                 
170
171             fillBuffer(0);
172         }
173
174         return buffer[boff++] & 0xff;
175
176     }
177
178     public int read(byte b[], int off, int len) throws IOException JavaDoc {
179         
180         // when stream has been read and eof reached, stream is closed
181
// and buffer is set to null ( see close() method)
182
// since stream cannot be re-used, check if stream is closed and
183
// if so throw an EOFException
184
if ( buffer == null )
185             throw new EOFException JavaDoc(MessageService.getTextMessage
186                     (SQLState.STREAM_EOF));
187
188         // first read
189
if (blen < 0)
190             fillBuffer(2);
191
192         int readCount = 0;
193
194         while (len > 0)
195         {
196
197             int copyBytes = blen - boff;
198
199             // buffer empty?
200
if (copyBytes == 0)
201             {
202                 if (eof)
203                 {
204                     if (readCount > 0)
205                     {
206                         return readCount;
207                     }
208                     else
209                     {
210                         // we have reached the eof, so close the stream
211
close();
212                         return -1;
213                     }
214                     
215                 }
216                 fillBuffer(0);
217                 continue;
218             }
219
220             if (len < copyBytes)
221                 copyBytes = len;
222
223             System.arraycopy(buffer, boff, b, off, copyBytes);
224             boff += copyBytes;
225             len -= copyBytes;
226             readCount += copyBytes;
227             off += copyBytes;
228
229         }
230         return readCount;
231     }
232
233     private void fillBuffer(int startingOffset) throws IOException JavaDoc
234     {
235         int off = boff = startingOffset;
236
237         if (off == 0)
238             multipleBuffer = true;
239
240         // 6! need to leave room for a three byte UTF8 encoding
241
// and 3 bytes for our special end of file marker.
242
for (; off <= buffer.length - 6; )
243         {
244             int c = reader.read();
245             if (c < 0) {
246                 eof = true;
247                 break;
248             }
249
250             if ((c >= 0x0001) && (c <= 0x007F))
251             {
252                 buffer[off++] = (byte) c;
253             }
254             else if (c > 0x07FF)
255             {
256                 buffer[off++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
257                 buffer[off++] = (byte) (0x80 | ((c >> 6) & 0x3F));
258                 buffer[off++] = (byte) (0x80 | ((c >> 0) & 0x3F));
259             }
260             else
261             {
262                 buffer[off++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
263                 buffer[off++] = (byte) (0x80 | ((c >> 0) & 0x3F));
264             }
265         }
266
267         blen = off;
268         boff = 0;
269
270         if (eof)
271             checkSufficientData();
272     }
273
274     /**
275      * Validate the length of the stream, take corrective action if allowed.
276      *
277      * JDBC 3.0 (from tutorial book) requires that an input stream has the
278      * correct number of bytes in the stream.
279      * If the stream is too long, trailing blank truncation is attempted if
280      * allowed. If truncation fails, or is disallowed, an exception is thrown.
281      *
282      * @throws IOException if an errors occurs in the application stream
283      * @throws DerbyIOException if Derby finds a problem with the stream;
284      * stream is too long and cannot be truncated, or the stream length
285      * does not match the specified length
286      */

287     private void checkSufficientData() throws IOException JavaDoc
288     {
289         // now that we finished reading from the stream; the amount
290
// of data that we can insert,start check for trailing spaces
291
if (charsToTruncate > 0)
292         {
293             reader.setLimit(charsToTruncate);
294             truncate();
295         }
296         
297         // A length less stream that is capped, will return 0 even if there
298
// are more bytes in the application stream.
299
int remainingBytes = reader.clearLimit();
300         if (remainingBytes > 0 && valueLength > 0) {
301             // If we had a specified length, throw exception.
302
throw new DerbyIOException(
303                     MessageService.getTextMessage(
304                         SQLState.SET_STREAM_INEXACT_LENGTH_DATA),
305                     SQLState.SET_STREAM_INEXACT_LENGTH_DATA);
306         }
307
308         // if we had a limit try reading one more character.
309
// JDBC 3.0 states the stream must have the correct number of
310
// characters in it.
311
if (remainingBytes == 0 && reader.read() >= 0) {
312             if (valueLength > -1) {
313                 throw new DerbyIOException(
314                         MessageService.getTextMessage(
315                             SQLState.SET_STREAM_INEXACT_LENGTH_DATA),
316                         SQLState.SET_STREAM_INEXACT_LENGTH_DATA);
317             } else {
318                 // Stream was capped (length less) and too long.
319
// Try to truncate if allowed, or else throw exception.
320
if (canTruncate()) {
321                     truncate();
322                 } else {
323                     throw new DerbyIOException(
324                             MessageService.getTextMessage(
325                                 SQLState.LANG_STRING_TRUNCATION),
326                             SQLState.LANG_STRING_TRUNCATION);
327                 }
328             }
329         }
330         
331         // can put the correct length into the stream.
332
if (!multipleBuffer)
333         {
334             int utflen = blen - 2;
335
336             buffer[0] = (byte) ((utflen >>> 8) & 0xFF);
337             buffer[1] = (byte) ((utflen >>> 0) & 0xFF);
338
339         }
340         else
341         {
342             buffer[blen++] = (byte) 0xE0;
343             buffer[blen++] = (byte) 0x00;
344             buffer[blen++] = (byte) 0x00;
345         }
346     }
347
348     /**
349      * Determine if trailing blank truncation is allowed.
350      */

351     private boolean canTruncate() {
352         // Only a few types can be truncated, default is to not allow.
353
if (typeName.equals(TypeId.CLOB_NAME)) {
354             return true;
355         } else if (typeName.equals(TypeId.VARCHAR_NAME)) {
356             return true;
357         }
358         return false;
359     }
360
361     /**
362      * Attempt to truncate the stream by removing trailing blanks.
363      */

364     private void truncate()
365             throws IOException JavaDoc {
366         int c = 0;
367         for (;;) {
368             c = reader.read();
369
370             if (c < 0) {
371                 break;
372             } else if (c != SPACE) {
373                 throw new DerbyIOException(
374                     MessageService.getTextMessage(
375                         SQLState.LANG_STRING_TRUNCATION,
376                         typeName,
377                         "XXXX",
378                         String.valueOf(valueLength)),
379                     SQLState.LANG_STRING_TRUNCATION);
380             }
381         }
382     }
383
384     /**
385      * return resources
386      */

387     public void close() throws IOException JavaDoc
388     {
389         // since stream has been read and eof reached, return buffer back to
390
// the vm.
391
// Instead of using another variable to indicate stream is closed
392
// a check on (buffer==null) is used instead.
393
buffer = null;
394     }
395
396     /**
397      * Return an optimized version of bytes available to read from
398      * the stream
399      * Note, it is not exactly per java.io.InputStream#available()
400      */

401     public final int available()
402     {
403        int remainingBytes = reader.getLimit();
404        // this object buffers BUFSIZE bytes that can be read
405
// and when that is finished it reads the next available bytes
406
// from the reader object
407
// reader.getLimit() returns the remaining bytes available
408
// on this stream
409
return (BUFSIZE > remainingBytes ? remainingBytes : BUFSIZE);
410     }
411   }
412
413
Popular Tags