001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.editor.ext;
043:
044: import java.io.File;
045: import java.io.IOException;
046: import java.io.RandomAccessFile;
047: import org.netbeans.editor.Analyzer;
048:
049: /**
050: * Management of storage of the data for the java completion.
051: *
052: * @author Miloslav Metelka, Martin Roskanin
053: * @version 1.00
054: */
055:
056: public class FileStorage {
057:
058: /** Constant for checking the maximum size of the string.
059: * If the string size exceeds this value the error is thrown
060: * as there's very likely corruption of the file.
061: */
062: private static final int MAX_STRING = 60000;
063:
064: private static final int BYTES_INCREMENT = 2048;
065:
066: private static final byte[] EMPTY_BYTES = new byte[0];
067:
068: Thread currentLock;
069:
070: protected boolean openedForWrite;
071:
072: public boolean fileNotFound = false;
073:
074: protected DataAccessor da;
075: protected boolean opened = false;
076:
077: /** Current offset in the bytes array */
078: protected int offset;
079:
080: /** Byte array holding the data that were read from file */
081: protected byte[] bytes = EMPTY_BYTES;
082:
083: /** Shared char array to use for reading strings */
084: char[] chars = Analyzer.EMPTY_CHAR_ARRAY;
085:
086: /** String cache */
087: StringCache strCache;
088:
089: /** How many times current writer requested writing */
090: private int lockDeep;
091:
092: /** file unlock without previous file lock */
093: private static final String WRITE_LOCK_MISSING = "Unlock file without previous lock file"; // NOI18N
094:
095: /** Version of read database file */
096: private int version = 1; // set to default version
097:
098: /** 7th bit
099: * 1 - more bytes were used for encoding of the int value
100: * 0 - only one byte has been used. The int value is less than 128.
101: */
102: private static final int BIT7 = (1 << 7);
103:
104: /** 5th and 6th bit
105: * 6th | 5th
106: * 0 | 0 - 1 byte will succed
107: * 0 | 1 - 2 bytes will succed
108: * 1 | 0 - 3 bytes will succed
109: * 1 | 1 - 4 bytes will succed
110: */
111: private static final int BIT6 = (1 << 6);
112: private static final int BIT5 = (1 << 5);
113:
114: /** @param fileName name of file to operate over
115: */
116: public FileStorage(String fileName) {
117: this (fileName, new StringCache());
118: }
119:
120: public FileStorage(String fileName, StringCache strCache) {
121: da = new FileAccessor(new File(fileName));
122: this .strCache = strCache;
123: }
124:
125: public FileStorage(DataAccessor da, StringCache strCache) {
126: this .da = da;
127: this .strCache = strCache;
128: }
129:
130: /** Setter for version of Code Completion DB file. */
131: public void setVersion(int ver) {
132: version = ver;
133: }
134:
135: public void open(boolean requestWrite) throws IOException {
136: if (openedForWrite == requestWrite) {
137: ensureOpen(requestWrite);
138: da.seek(getFileLength());
139: return; // already opened with correct type
140: } else { // opened with different type
141: close();
142: }
143:
144: // open the file
145: ensureOpen(requestWrite);
146: da.seek(getFileLength());
147: openedForWrite = requestWrite;
148: offset = 0;
149: }
150:
151: private void ensureOpen(boolean requestWrite) throws IOException {
152: if (!opened) {
153: da.open(requestWrite);
154: opened = true;
155: }
156: }
157:
158: public void close() throws IOException {
159: opened = false;
160: da.close();
161: }
162:
163: /** Check size of bytes[] array */
164: protected void checkBytesSize(int len) {
165: if (bytes.length < len) {
166: byte[] newBytes = new byte[len + BYTES_INCREMENT];
167: System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
168: bytes = newBytes;
169: }
170: }
171:
172: /** Read some part of the file into the begining of bytes array
173: * and reset offset to zero.
174: */
175: public void read(int len) throws IOException {
176: checkBytesSize(len);
177: da.read(bytes, 0, len);
178: offset = 0;
179: }
180:
181: /** Write bytes array (with offset length) to the file */
182: public void write() throws IOException {
183: if (offset > 0) {
184: da.append(bytes, 0, offset);
185: }
186: offset = 0;
187: }
188:
189: public void seek(int filePointer) throws IOException {
190: da.seek(filePointer);
191: }
192:
193: public String getFileName() {
194: return da.toString();
195: }
196:
197: public int getFilePointer() throws IOException {
198: return (int) da.getFilePointer();
199: }
200:
201: public void setOffset(int offset) {
202: this .offset = offset;
203: }
204:
205: public int getOffset() {
206: return offset;
207: }
208:
209: public int getFileLength() throws IOException {
210: return da.getFileLength();
211: }
212:
213: public void resetBytes() {
214: bytes = EMPTY_BYTES;
215: }
216:
217: /** Reset the size of the file and set current offset to zero. */
218: public void resetFile() throws IOException {
219: open(true);
220: offset = 0;
221: da.resetFile();
222: close();
223: }
224:
225: /** Get the integer value from the bytes[] array */
226: public int getInteger() {
227: if (version == 1) {
228: int i = bytes[offset++];
229: i = (i << 8) + (bytes[offset++] & 255);
230: i = (i << 8) + (bytes[offset++] & 255);
231: i = (i << 8) + (bytes[offset++] & 255);
232: return i;
233: }
234:
235: if (version == 2) {
236: return decodeInteger();
237: }
238: return 0;
239: }
240:
241: /** Get the string value from the bytes[] array */
242: public String getString() {
243: int len = getInteger(); // length of string
244:
245: if (len < 0) {
246: throw new RuntimeException(
247: "Consistency error: read string length=" + len); // NOI18N
248: }
249:
250: if (len > MAX_STRING) {
251: throw new RuntimeException("FileStorage: String len is "
252: + len // NOI18N
253: + ". There's probably a corruption in the file '" // NOI18N
254: + getFileName() + "'."); // NOI18N
255: }
256:
257: if (version == 1) {
258: if (chars.length < len) { // check chars array size
259: chars = new char[2 * len];
260: }
261: for (int i = 0; i < len; i++) {
262: chars[i] = (char) ((bytes[offset] << 8) + (bytes[offset + 1] & 255));
263: offset += 2;
264: }
265:
266: String s = null;
267: if (len >= 0) {
268: if (strCache != null) {
269: s = strCache.getString(chars, 0, len);
270: } else { // no string cache
271: s = new String(chars, 0, len);
272: }
273: }
274:
275: return s;
276:
277: } else if (version == 2) {
278: try {
279: String s = new String(bytes, offset, len, getEncoding());
280: offset += len;
281: return s;
282: } catch (java.io.UnsupportedEncodingException e) {
283: e.printStackTrace();
284: return "";
285: } catch (ArrayIndexOutOfBoundsException ex) {
286: StringBuffer sb = new StringBuffer(len);
287: for (int i = 0; i < len; i++) {
288: sb.append((char) bytes[offset + i]);
289: }
290: String st = sb.toString();
291:
292: throw new RuntimeException(
293: "Debug of #12932: If this bug occurs, please send the stacktrace as attachment to Issuezilla's #12932."
294: + "\n"
295: + // NOI18N
296: "http://www.netbeans.org/issues/show_bug.cgi?id=12932"
297: + "\n" + // NOI18N
298: "debug 2" + "\n" + // NOI18N
299: "File:" + this .toString() + "\n" + // NOI18N
300: "File Version:" + version + "\n" + // NOI18N
301: "Offest: " + offset + "\n" + // NOI18N
302: "Read length: " + len + "\n" + // NOI18N
303: "bytes.length: " + bytes.length + "\n" + // NOI18N
304: "String:" + st + "\n" + // NOI18N
305: "Error:" + ex); // NOI18N
306: }
307: }
308: return "";
309: }
310:
311: /** Put the integer into bytes[] array. It is stored as four bytes
312: * in big endian.
313: */
314: public void putInteger(int i) {
315: if (version == 1) {
316: checkBytesSize(offset + 4); // int size
317: bytes[offset + 3] = (byte) (i & 255);
318: i >>>= 8;
319: bytes[offset + 2] = (byte) (i & 255);
320: i >>>= 8;
321: bytes[offset + 1] = (byte) (i & 255);
322: i >>>= 8;
323: bytes[offset] = (byte) i;
324: offset += 4;
325: }
326:
327: if (version == 2) {
328: encodeInteger(i);
329: }
330: }
331:
332: /** Put the string into bytes[] array. First the length is stored
333: * by putInteger() and then all the characters as two bytes each in big
334: * endian.
335: */
336: public void putString(String s) {
337: if (s == null) {
338: return;
339: }
340:
341: if (version == 1) {
342: int len = s.length();
343: putInteger(len);
344:
345: if (len > 0) {
346: checkBytesSize(offset + len * 2);
347: for (int i = 0; i < len; i++) {
348: char ch = s.charAt(i);
349: bytes[offset + 1] = (byte) (ch & 255);
350: ch >>>= 8;
351: bytes[offset] = (byte) (ch & 255);
352: offset += 2;
353: }
354: }
355: } else if (version == 2) {
356: /* Encode string to appropriate byte array
357: * according to the version of file */
358: byte encodedBytes[];
359: try {
360: encodedBytes = s.getBytes(getEncoding());
361: } catch (java.io.UnsupportedEncodingException e) {
362: return;
363: }
364:
365: /* put the length of encoded byte array */
366: int len = java.lang.reflect.Array.getLength(encodedBytes);
367: if (len < 0) {
368: return;
369: }
370: putInteger(len);
371:
372: checkBytesSize(offset + len);
373: System.arraycopy(encodedBytes, 0, bytes, offset, len);
374: offset += len;
375: }
376: }
377:
378: /** Returns decoded integer */
379: private int decodeInteger() {
380: int i = bytes[offset++] & 255;
381: if ((i & BIT7) == 0) {
382: return i;
383: }
384: int level = 1;
385: if ((i & BIT6) != 0)
386: level += 2;
387: if ((i & BIT5) != 0)
388: level += 1;
389: i &= ~(BIT7 | BIT6 | BIT5); // reset first three bits.
390:
391: for (int j = 1; j <= level; j++) {
392: i = (i << 8) + (bytes[offset++] & 255);
393: }
394: return i;
395: }
396:
397: /** Encodes the given Integer */
398: private void encodeInteger(int y) {
399: int level = 0;
400:
401: if (y >= 536870912)
402: level += 4; //256*256*256*32
403: else if (y >= 2097152)
404: level += 3; //256*256*32
405: else if (y >= 8192)
406: level += 2; //256*32
407: else if (y >= 128)
408: level += 1; //128
409:
410: checkBytesSize(offset + level + 1); // adjust the byte array
411:
412: for (int j = level; j > 0; j--) {
413: bytes[offset + j] = (byte) (y & 255);
414: y >>>= 8;
415: }
416:
417: bytes[offset] = (byte) y;
418:
419: // set compression type bits.
420: switch (level) {
421: case 2:
422: bytes[offset] |= BIT5;
423: break;
424: case 3:
425: bytes[offset] |= BIT6;
426: break;
427: case 4:
428: bytes[offset] |= BIT5;
429: bytes[offset] |= BIT6;
430: break;
431: }
432: if (level > 0)
433: bytes[offset] |= BIT7; // Setting compression flag
434: level++;
435: offset += level;
436: }
437:
438: /** Get encoding according to file version */
439: private String getEncoding() {
440: switch (version) {
441: case 1:
442: return "UTF-16BE"; //NOI18N
443: case 2:
444: return "UTF-8"; //NOI18N
445: default:
446: return "UTF-16BE"; //NOI18N
447: }
448: }
449:
450: /** Locks the file and disable other threads to write */
451: public synchronized final void lockFile() {
452: if ((currentLock == null)
453: || (Thread.currentThread() != currentLock)) {
454: try {
455: if (currentLock == null) {
456: currentLock = Thread.currentThread();
457: lockDeep = 0;
458: } else {
459: wait();
460: }
461: } catch (InterruptedException ie) {
462: throw new RuntimeException(ie.toString());
463: } catch (IllegalMonitorStateException imse) {
464: throw new RuntimeException(imse.toString());
465: }
466: } else { // inner locking block
467: lockDeep++; // only increase write deepness
468: }
469: }
470:
471: /** Unlocks the file and notifies wqiting threads */
472: public synchronized final void unlockFile() {
473: if (Thread.currentThread() != currentLock) {
474: throw new RuntimeException(WRITE_LOCK_MISSING);
475: }
476: if (lockDeep == 0) { // most outer locking block
477: resetBytes();
478: notify();
479: currentLock = null;
480: } else { // just inner locking block
481: lockDeep--;
482: }
483: }
484:
485: /** Returns name of the file */
486: public String toString() {
487: return getFileName();
488: }
489:
490: }
|