/*******************************************************************************
* Copyright (c) 2006, 2016 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* QNX - Initial API and implementation
* Andrew Ferguson (Symbian)
* Markus Schorn (Wind River Systems)
*******************************************************************************/
package org.eclipse.jdt.internal.core.nd.db;
import org.eclipse.jdt.internal.core.nd.util.CharArrayUtils;
/**
* This is for strings that fit inside a single chunk.
*/
public class ShortString implements IString {
private final Database db;
private final long record;
private int hash;
private static final int LENGTH = 0;
private static final int CHARS = 4;
public static final int MAX_BYTE_LENGTH = Database.MAX_SINGLE_BLOCK_MALLOC_SIZE - CHARS;
public ShortString(Database db, long offset) {
this.db = db;
this.record = offset;
}
public ShortString(Database db, char[] chars, boolean useBytes) throws IndexException {
final int n = chars.length;
this.db = db;
this.record = db.malloc(CHARS + (useBytes ? n : 2 * n), Database.POOL_STRING_SHORT);
Chunk chunk = db.getChunk(this.record);
chunk.putInt(this.record + LENGTH, useBytes ? -n : n);
long p = this.record + CHARS;
if (useBytes) {
chunk.putCharsAsBytes(p, chars, 0, n);
} else {
chunk.putChars(p, chars, 0, n);
}
}
@Override
public long getRecord() {
return this.record;
}
@Override
public void delete() throws IndexException {
this.db.free(this.record, Database.POOL_STRING_SHORT);
}
@Override
public char[] getChars() throws IndexException {
final Chunk chunk = this.db.getChunk(this.record);
final int l = chunk.getInt(this.record + LENGTH);
final int length = Math.abs(l);
final char[] chars = new char[length];
if (l < 0) {
chunk.getCharsFromBytes(this.record + CHARS, chars, 0, length);
} else {
chunk.getChars(this.record + CHARS, chars, 0, length);
}
return chars;
}
@Override
public String getString() throws IndexException {
return new String(getChars());
}
@Override
public boolean equals(Object obj) {
if (obj == this)
return true;
try {
if (obj instanceof ShortString) {
ShortString string = (ShortString)obj;
if (this.db == string.db && this.record == string.record)
return true;
Chunk chunk1 = this.db.getChunk(this.record);
Chunk chunk2 = string.db.getChunk(string.record);
int n1 = chunk1.getInt(this.record);
int n2 = chunk2.getInt(string.record);
if (n1 != n2)
return false;
return CharArrayUtils.equals(getChars(), string.getChars());
}
if (obj instanceof char[]) {
char[] chars = (char[])obj;
// Make sure size is the same
if (length() != chars.length)
return false;
return CharArrayUtils.equals(getChars(), chars);
} else if (obj instanceof String) {
String string = (String)obj;
if (length() != string.length())
return false;
return CharArrayUtils.equals(getChars(), string.toCharArray());
}
} catch (IndexException e) {
Package.log(e);
}
return false;
}
/**
* Compatible with {@link String#hashCode()}
*/
@Override
public int hashCode() {
int h = this.hash;
if (h == 0) {
char chars[];
chars = getChars();
final int len = chars.length;
for (int i = 0; i < len; i++) {
h = 31 * h + chars[i];
}
this.hash = h;
}
return h;
}
public static int compare(final char[] chars, char[] other, boolean caseSensitive) {
final int n = Math.min(chars.length, other.length);
for (int i = 0; i < n; i++) {
int cmp= compareChars(chars[i], other[i], caseSensitive);
if (cmp != 0)
return cmp;
}
return chars.length - other.length;
}
@Override
public int compare(char[] other, boolean caseSensitive) throws IndexException {
return compare(getChars(), other, caseSensitive);
}
@Override
public int compare(IString string, boolean caseSensitive) throws IndexException {
return compare(getChars(), string.getChars(), caseSensitive);
}
@Override
public int compare(String other, boolean caseSensitive) throws IndexException {
return compare(getChars(), other.toCharArray(), caseSensitive);
}
@Override
public int compareCompatibleWithIgnoreCase(IString string) throws IndexException {
return compareCompatibleWithIgnoreCase(string.getChars());
}
@Override
public int compareCompatibleWithIgnoreCase(char[] other) throws IndexException {
return compareCompatibleWithIgnoreCase(getChars(), other);
}
public static int compareCompatibleWithIgnoreCase(final char[] chars, char[] other) {
final int n = Math.min(chars.length, other.length);
int sensitiveCmp= 0;
for (int i = 0; i < n; i++) {
final char c1= chars[i];
final char c2= other[i];
if (c1 != c2) {
int cmp= compareChars(c1, c2, false); // insensitive
if (cmp != 0)
return cmp;
if (sensitiveCmp == 0) {
if (c1 < c2) {
sensitiveCmp= -1;
} else {
sensitiveCmp= 1;
}
}
}
}
int cmp= chars.length - other.length;
if (cmp != 0)
return cmp;
return sensitiveCmp;
}
@Override
public int comparePrefix(char[] other, boolean caseSensitive) throws IndexException {
return comparePrefix(getChars(), other, caseSensitive);
}
public static int comparePrefix(final char[] chars, char[] other, boolean caseSensitive) {
final int n = Math.min(chars.length, other.length);
for (int i = 0; i < n; i++) {
int cmp= compareChars(chars[i], other[i], caseSensitive);
if (cmp != 0)
return cmp;
}
if (chars.length < other.length)
return -1;
return 0;
}
/**
* Compare characters case-sensitively, or case-insensitively.
*
* <b>Limitation</b> This only maps the range a-z,A-Z onto each other
* @param a a character
* @param b a character
* @param caseSensitive whether to compare case-sensitively
* @return
* <ul>
* <li>-1 if a < b
* <li>0 if a == b
* <li>1 if a > b
* </ul>
*/
public static int compareChars(char a, char b, boolean caseSensitive) {
if (caseSensitive) {
if (a < b)
return -1;
if (a > b)
return 1;
} else {
if (a != b) {
a= a >= 'a' && a <='z' ? (char) (a - 32) : a;
b= b >= 'a' && b <='z' ? (char) (b - 32) : b;
if (a < b)
return -1;
if (a > b)
return 1;
}
}
return 0;
}
/* TODO - this is more correct than the above implementation, but we need to
* benchmark first.
*
* public static int compareChars(char a, char b, boolean caseSensitive) {
if (caseSensitive) {
if (a < b)
return -1;
if (a > b)
return 1;
} else {
if (a != b) {
a = Character.toUpperCase(a);
b = Character.toUpperCase(b);
if (a != b) {
a = Character.toLowerCase(a);
b = Character.toLowerCase(b);
if (a != b) {
if (a < b)
return -1;
if (a > b)
return 1;
}
}
}
}
return 0;
}
*/
@Override
public String toString() {
try {
return getString();
} catch (IndexException e) {
return super.toString();
}
}
@Override
public int length() {
return Math.abs(this.db.getInt(this.record + LENGTH));
}
}