// $Id$
package diskCacheV111.util;
import com.google.common.hash.Funnel;
import com.google.common.hash.PrimitiveSink;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Immutable representation of a pnfsId
*/
public class PnfsId implements Serializable, Comparable<PnfsId> {
private static final String SIMPLE_PNFS_ID_REGEX = "\\p{XDigit}{1,24}";
// FIXME STAR_PNFS_ID_REGEX is too permissive; overall length should be 24 or less.
private static final String STAR_PNFS_ID_REGEX = "\\p{XDigit}{1,22}\\*\\p{XDigit}{1,22}";
private static final String PNFS_ID_REGEX = '(' + SIMPLE_PNFS_ID_REGEX + '|' + STAR_PNFS_ID_REGEX + ')';
private static final String PNFS_STRING_REGEX = '(' + PNFS_ID_REGEX + "(\\..*)?)";
private static final String CHIMERA_ID_REGEX = "\\p{XDigit}{36}";
private static final String VALID_ID_REGEX = "^(" + PNFS_STRING_REGEX + '|' + CHIMERA_ID_REGEX + ")$";
private static final Pattern VALID_ID_PATTERN = Pattern.compile( VALID_ID_REGEX);
private static final int OLD_ID_SIZE = 12; // original pnfs
private static final int NEW_ID_SIZE = 18; // chimera
private final byte[] _a;
private final String _domain;
private static final long serialVersionUID = -112220393521303857L;
public static boolean isValid( String id) {
Matcher m = VALID_ID_PATTERN.matcher( id);
return m.matches();
}
public PnfsId(String id, String domain) {
this(_stringToBytes(id), domain);
}
public PnfsId(String s) {
this(stringToId(s), stringToDomain(s));
}
public PnfsId(byte[] id) {
this(id, null);
}
public PnfsId(byte[] id, String domain) {
int length = id.length;
if (length != OLD_ID_SIZE && length != NEW_ID_SIZE) {
throw new IllegalArgumentException("Illegal pnfsid string length");
}
_a = new byte[length];
System.arraycopy(id, 0, _a, 0, length);
_domain = (domain != null) ? domain.intern() : null;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof PnfsId)) {
return false;
}
PnfsId other = (PnfsId) o;
return Arrays.equals(_a, other._a)
&& Objects.equals(_domain, other._domain);
}
@Override
public int hashCode() {
return Arrays.hashCode(_a) ^ ((_domain == null) ? 0 : _domain.hashCode());
}
@Override
public int compareTo(PnfsId pnfsId) {
if( pnfsId == this ) {
return 0;
}
int i;
for (i = 0; (i < _a.length) && (_a[i] == pnfsId._a[i]); i++) {
}
if (i == _a.length) {
return 0;
}
int t = _a[i] < 0 ? 256 + _a[i] : _a[i];
int o = pnfsId._a[i] < 0 ? 256 + pnfsId._a[i] : pnfsId._a[i];
return t < o ? -1 : 1;
}
public int getDatabaseId() {
return (((_a[0]) & 0xFF) << 8) | ((_a[1]) & 0xFF);
}
public String getDomain() {
return _domain;
}
public String getId() {
return bytesToHexString(_a);
}
@Override
public String toString() {
return getId() + ((_domain != null) ? '.' + _domain : "");
}
public String toIdString() {
return getId();
}
public static String toCompleteId(String shortId) {
return bytesToHexString(_stringToBytes(shortId));
}
public byte[] getBytes() {
byte[] x = new byte[_a.length];
System.arraycopy(_a, 0, x, 0, _a.length);
return x;
}
public String toShortString() {
StringBuilder sb = new StringBuilder();
int i;
for (i = 0; i < 2; i++) {
sb.append(byteToHexString(_a[i]));
}
for (; (i < _a.length) && (_a[i] == 0); i++) {
}
for (; i < _a.length; i++) {
sb.append(byteToHexString(_a[i]));
}
return sb.toString();
}
private static byte[] stringToId(String s) {
int i = s.indexOf('.');
if (i < 0) {
return _stringToBytes(s);
} else {
return _stringToBytes(s.substring(0, i));
}
}
private static String stringToDomain(String s) {
int i = s.indexOf('.');
if (i < 0 || i == s.length() - 1) {
return null;
} else {
return s.substring(i + 1);
}
}
private static String byteToHexString(byte b) {
String s = Integer.toHexString((b < 0) ? (256 + b) : (int) b)
.toUpperCase();
if (s.length() == 1) {
return '0' + s;
} else {
return s;
}
}
/**
* Translation table used by bytesToHexString.
*/
private static final char[] valueToHex = {
'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B',
'C', 'D', 'E', 'F'};
/**
* Converts a byte array into the string representation in base 16.
*/
private static String bytesToHexString(byte[] b) {
char[] result = new char[2 * b.length];
for (int i = 0; i < b.length; i++) {
int value = (b[i] + 0x100) & 0xFF;
result[2 * i] = valueToHex[value >> 4];
result[2 * i + 1] = valueToHex[value & 0x0F];
}
return new String(result);
}
private static byte[] _stringToBytes(String idString) {
int len = idString.length();
int idSize;
switch (len) {
case OLD_ID_SIZE * 2: // old pnfsid
idSize = OLD_ID_SIZE;
break;
case NEW_ID_SIZE * 2: // himera
idSize = NEW_ID_SIZE;
break;
default:
// all id's shorter than 24 characters will be extended to 24
if ((len > OLD_ID_SIZE * 2) || (len == 0)) {
throw new IllegalArgumentException(
"Illegal pnfsid string length");
}
idSize = OLD_ID_SIZE;
}
byte[] a = new byte[idSize];
int p = idString.indexOf('*');
if (p > -1) {
if ((p == 0) || (p == (idString.length() - 1))) {
throw new IllegalArgumentException("Illegal use of *");
}
int diff = 2 * OLD_ID_SIZE - idString.length() + 1;
StringBuilder sb = new StringBuilder();
sb.append(idString.substring(0, p));
for (int i = 0; i < diff; i++) {
sb.append('0');
}
sb.append(idString.substring(p + 1));
idString = sb.toString();
}
if (idString.length() > (2 * a.length)) {
throw new IllegalArgumentException("Illegal pnfsid string length");
} else if (idString.length() < (2 * a.length)) {
StringBuilder sb = new StringBuilder();
int m = 2 * OLD_ID_SIZE - idString.length();
for (int i = 0; i < m; i++) {
sb.append('0');
}
sb.append(idString);
idString = sb.toString();
}
for (int i = 0; i < idSize; i++) {
int l = Integer
.parseInt(idString.substring(2 * i, 2 * (i + 1)), 16);
a[i] = (byte) ((l < 128) ? l : (l - 256));
}
return a;
}
/**
* Converts string representation of pnfsid into its internal binary form
*
* @return pnfsid as byte array
*/
public byte[] toBinPnfsId() {
switch (_a.length) {
case OLD_ID_SIZE: // old pnfsid
return toBinPnfsId(getId());
case NEW_ID_SIZE: // chimera
return getBytes();
default:
return null;
}
}
private static int hexToValue(byte val) {
if (val >= '0' && val <= '9') {
val -= '0';
return val;
} else if (val >= 'a' && val <= 'f') {
val -= 'a' - 10;
return val;
} else if (val >= 'A' && val <= 'F') {
val -= 'A' - 10;
return val;
}
return -1;
}
/**
* Converts string representation of pnfsid into its internal binary form used by PNFS server
*
* @param pnfsid as String
* @return pnfsid as byte array
*/
private static byte[] toBinPnfsId(String pnfsid) {
byte[] bb = pnfsid.getBytes();
byte[] ba = new byte[bb.length/2];
//
ba[ 0] = (byte)(hexToValue(bb[ 2])<<4 | hexToValue(bb[ 3]));
ba[ 1] = (byte)(hexToValue(bb[ 0])<<4 | hexToValue(bb[ 1]));
//
ba[ 2] = (byte)(hexToValue(bb[ 6])<<4 | hexToValue(bb[ 7]));
ba[ 3] = (byte)(hexToValue(bb[ 4])<<4 | hexToValue(bb[ 5]));
//
ba[ 4] = (byte)(hexToValue(bb[14])<<4 | hexToValue(bb[15]));
ba[ 5] = (byte)(hexToValue(bb[12])<<4 | hexToValue(bb[13]));
ba[ 6] = (byte)(hexToValue(bb[10])<<4 | hexToValue(bb[11]));
ba[ 7] = (byte)(hexToValue(bb[ 8])<<4 | hexToValue(bb[ 9]));
//
ba[ 8] = (byte)(hexToValue(bb[22])<<4 | hexToValue(bb[23]));
ba[ 9] = (byte)(hexToValue(bb[20])<<4 | hexToValue(bb[21]));
ba[10] = (byte)(hexToValue(bb[18])<<4 | hexToValue(bb[19]));
ba[11] = (byte)(hexToValue(bb[16])<<4 | hexToValue(bb[17]));
return ba;
}
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("USAGE : ... <pnfsId>");
System.exit(4);
}
try {
PnfsId id = new PnfsId(args[0]);
System.out.println("id.toString() " + id);
System.out.println("id.getId() " + id.getId());
System.out.println("db.getDatabaseId() " + id.getDatabaseId());
System.out.println("db.getDomain() " + id.getDomain());
System.out.println("id.getBytes() " + Arrays.toString(id.getBytes()));
System.out.println("id.toBinPnfsId() " + Arrays.toString(id.toBinPnfsId()));
System.exit(0);
} catch (Exception e) {
e.printStackTrace();
System.exit(4);
}
}
public static Funnel<PnfsId> funnel()
{
return PnfsIdFunnel.INSTANCE;
}
private enum PnfsIdFunnel implements Funnel<PnfsId> {
INSTANCE;
@Override
public void funnel(PnfsId from, PrimitiveSink into) {
if (from._domain != null) {
into.putString(from._domain, StandardCharsets.US_ASCII);
}
into.putBytes(from._a);
}
}
}