/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.editor.ext;
import org.netbeans.editor.Analyzer;
import java.io.File;
import java.io.IOException;
/**
* Management of storage of the data for the java completion.
*
* @author Miloslav Metelka, Martin Roskanin
* @version 1.00
*/
public class FileStorage {
/** Constant for checking the maximum size of the string.
* If the string size exceeds this value the error is thrown
* as there's very likely corruption of the file.
*/
private static final int MAX_STRING = 60000;
private static final int BYTES_INCREMENT = 2048;
private static final byte[] EMPTY_BYTES = new byte[0];
Thread currentLock;
protected boolean openedForWrite;
public boolean fileNotFound = false;
protected DataAccessor da;
protected boolean opened = false;
/** Current offset in the bytes array */
protected int offset;
/** Byte array holding the data that were read from file */
protected byte[] bytes = EMPTY_BYTES;
/** Shared char array to use for reading strings */
char[] chars = Analyzer.EMPTY_CHAR_ARRAY;
/** String cache */
StringCache strCache;
/** How many times current writer requested writing */
private int lockDeep;
/** file unlock without previous file lock */
private static final String WRITE_LOCK_MISSING
= "Unlock file without previous lock file"; // NOI18N
/** Version of read database file */
private int version = 1; // set to default version
/** 7th bit
* 1 - more bytes were used for encoding of the int value
* 0 - only one byte has been used. The int value is less than 128.
*/
private static final int BIT7 = (1 << 7);
/** 5th and 6th bit
* 6th | 5th
* 0 | 0 - 1 byte will succed
* 0 | 1 - 2 bytes will succed
* 1 | 0 - 3 bytes will succed
* 1 | 1 - 4 bytes will succed
*/
private static final int BIT6 = (1 << 6);
private static final int BIT5 = (1 << 5);
/** @param fileName name of file to operate over
*/
public FileStorage(String fileName) {
this(fileName, new StringCache());
}
public FileStorage(String fileName, StringCache strCache) {
da = new FileAccessor(new File(fileName));
this.strCache = strCache;
}
public FileStorage(DataAccessor da, StringCache strCache){
this.da = da;
this.strCache = strCache;
}
/** Setter for version of Code Completion DB file. */
public void setVersion(int ver){
version = ver;
}
public void open(boolean requestWrite) throws IOException {
if (openedForWrite == requestWrite) {
ensureOpen(requestWrite);
da.seek(getFileLength());
return; // already opened with correct type
} else { // opened with different type
close();
}
// open the file
ensureOpen(requestWrite);
da.seek(getFileLength());
openedForWrite = requestWrite;
offset = 0;
}
private void ensureOpen(boolean requestWrite) throws IOException {
if (!opened) {
da.open(requestWrite);
opened = true;
}
}
public void close() throws IOException {
opened = false;
da.close();
}
/** Check size of bytes[] array */
protected void checkBytesSize(int len) {
if (bytes.length < len) {
byte[] newBytes = new byte[len + BYTES_INCREMENT];
System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
bytes = newBytes;
}
}
/** Read some part of the file into the begining of bytes array
* and reset offset to zero.
*/
public void read(int len) throws IOException {
checkBytesSize(len);
da.read(bytes, 0, len);
offset = 0;
}
/** Write bytes array (with offset length) to the file */
public void write() throws IOException {
if (offset > 0) {
da.append(bytes, 0, offset);
}
offset = 0;
}
public void seek(int filePointer) throws IOException {
da.seek(filePointer);
}
public String getFileName() {
return da.toString();
}
public int getFilePointer() throws IOException {
return (int)da.getFilePointer();
}
public void setOffset(int offset) {
this.offset = offset;
}
public int getOffset() {
return offset;
}
public int getFileLength() throws IOException {
return da.getFileLength();
}
public void resetBytes() {
bytes = EMPTY_BYTES;
}
/** Reset the size of the file and set current offset to zero. */
public void resetFile() throws IOException {
open(true);
offset = 0;
da.resetFile();
close();
}
/** Get the integer value from the bytes[] array */
public int getInteger() {
if (version == 1){
int i = bytes[offset++];
i = (i << 8) + (bytes[offset++] & 255);
i = (i << 8) + (bytes[offset++] & 255);
i = (i << 8) + (bytes[offset++] & 255);
return i;
}
if (version == 2){
return decodeInteger();
}
return 0;
}
/** Get the string value from the bytes[] array */
public String getString() {
int len = getInteger(); // length of string
if (len < 0) {
throw new RuntimeException("Consistency error: read string length=" + len); // NOI18N
}
if (len > MAX_STRING) {
throw new RuntimeException("FileStorage: String len is " + len // NOI18N
+ ". There's probably a corruption in the file '" // NOI18N
+ getFileName() + "'."); // NOI18N
}
if(version == 1){
if (chars.length < len) { // check chars array size
chars = new char[2 * len];
}
for (int i = 0; i < len; i++) {
chars[i] = (char)((bytes[offset] << 8) + (bytes[offset + 1] & 255));
offset += 2;
}
String s = null;
if (len >= 0) {
if (strCache != null) {
s = strCache.getString(chars, 0, len);
} else { // no string cache
s = new String(chars, 0, len);
}
}
return s;
}else if (version == 2){
try{
String s = new String(bytes,offset,len,getEncoding());
offset += len;
return s;
}catch(java.io.UnsupportedEncodingException e){
e.printStackTrace();
return "";
}
catch(ArrayIndexOutOfBoundsException ex){
StringBuffer sb = new StringBuffer(len);
for (int i=0;i<len;i++){
sb.append((char)bytes[offset+i]);
}
String st = sb.toString();
throw new RuntimeException("Debug of #12932: If this bug occurs, please send the stacktrace as attachment to Issuezilla's #12932."+"\n"+
"http://www.netbeans.org/issues/show_bug.cgi?id=12932"+"\n"+
"debug 2"+"\n"+
"File:"+this.toString()+"\n"+
"File Version:"+version+"\n"+
"Offest: "+offset+"\n"+
"Read length: "+len+"\n"+
"bytes.length: "+bytes.length+"\n"+
"String:"+st+"\n"+
"Error:"+ex);
}
}
return "";
}
/** Put the integer into bytes[] array. It is stored as four bytes
* in big endian.
*/
public void putInteger(int i) {
if (version == 1){
checkBytesSize(offset + 4); // int size
bytes[offset + 3] = (byte)(i & 255);
i >>>= 8;
bytes[offset + 2] = (byte)(i & 255);
i >>>= 8;
bytes[offset + 1] = (byte)(i & 255);
i >>>= 8;
bytes[offset] = (byte)i;
offset += 4;
}
if (version == 2){
encodeInteger(i);
}
}
/** Put the string into bytes[] array. First the length is stored
* by putInteger() and then all the characters as two bytes each in big
* endian.
*/
public void putString(String s) {
if (s == null) {
return;
}
if (version == 1){
int len = s.length();
putInteger(len);
if (len > 0) {
checkBytesSize(offset + len * 2);
for (int i = 0; i < len; i++) {
char ch = s.charAt(i);
bytes[offset + 1] = (byte)(ch & 255);
ch >>>= 8;
bytes[offset] = (byte)(ch & 255);
offset += 2;
}
}
}else if (version == 2){
/* Encode string to appropriate byte array
* according to the version of file */
byte encodedBytes[];
try{
encodedBytes = s.getBytes(getEncoding());
}catch(java.io.UnsupportedEncodingException e){
return;
}
/* put the length of encoded byte array */
int len = java.lang.reflect.Array.getLength(encodedBytes);
if (len < 0) {
return;
}
putInteger(len);
checkBytesSize(offset + len);
System.arraycopy(encodedBytes,0,bytes,offset,len);
offset += len;
}
}
/** Returns decoded integer */
private int decodeInteger(){
int i = bytes[offset++]&255;
if ((i & BIT7) == 0){
return i;
}
int level = 1;
if ((i & BIT6)!= 0) level +=2;
if ((i & BIT5)!= 0) level +=1;
i &= ~(BIT7 | BIT6 | BIT5); // reset first three bits.
for(int j=1; j<=level; j++){
i = (i << 8) + (bytes[offset++]&255);
}
return i;
}
/** Encodes the given Integer */
private void encodeInteger(int y){
int level = 0;
if (y >= 536870912) level += 4; //256*256*256*32
else if (y >= 2097152) level += 3; //256*256*32
else if (y >= 8192) level += 2; //256*32
else if (y >= 128) level += 1; //128
checkBytesSize(offset + level + 1); // adjust the byte array
for (int j=level; j>0; j--){
bytes[offset+j] = (byte) (y & 255);
y >>>= 8;
}
bytes[offset] = (byte) y;
// set compression type bits.
switch( level ) {
case 2:
bytes[offset] |= BIT5;
break;
case 3:
bytes[offset] |= BIT6;
break;
case 4:
bytes[offset] |= BIT5;
bytes[offset] |= BIT6;
break;
}
if (level > 0) bytes[offset] |= BIT7; // Setting compression flag
level++;
offset += level;
}
/** Get encoding according to file version */
private String getEncoding(){
switch( version ) {
case 1:
return "UTF-16BE"; //NOI18N
case 2:
return "UTF-8"; //NOI18N
default:
return "UTF-16BE"; //NOI18N
}
}
/** Locks the file and disable other threads to write */
public synchronized final void lockFile() {
if ((currentLock == null) || (Thread.currentThread() != currentLock)) {
try{
if (currentLock == null){
currentLock = Thread.currentThread();
lockDeep = 0;
}else{
wait();
}
}catch(InterruptedException ie){
throw new RuntimeException(ie.toString());
}catch(IllegalMonitorStateException imse){
throw new RuntimeException(imse.toString());
}
} else { // inner locking block
lockDeep++; // only increase write deepness
}
}
/** Unlocks the file and notifies wqiting threads */
public synchronized final void unlockFile() {
if (Thread.currentThread() != currentLock) {
throw new RuntimeException(WRITE_LOCK_MISSING);
}
if (lockDeep == 0) { // most outer locking block
resetBytes();
notify();
currentLock=null;
} else { // just inner locking block
lockDeep--;
}
}
/** Returns name of the file */
public String toString(){
return getFileName();
}
}