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