/** *Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] *Licensed under the Apache License, Version 2.0 (the "License"); *you may not use this file except in compliance with the License. *You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 *Unless required by applicable law or agreed to in writing, *software distributed under the License is distributed on an "AS IS" BASIS, *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, *either express or implied. See the License for the specific language governing permissions and limitations under the License */ package net.rubyeye.xmemcached.utils; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import net.rubyeye.xmemcached.codec.MemcachedDecoder; import net.rubyeye.xmemcached.monitor.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.code.yanf4j.buffer.IoBuffer; /** * Utilities for byte process * * @author dennis * */ public final class ByteUtils { public static final Logger log = LoggerFactory.getLogger(ByteUtils.class); public static final String DEFAULT_CHARSET_NAME = "utf-8"; public static final Charset DEFAULT_CHARSET = Charset .forName(DEFAULT_CHARSET_NAME); public static final ByteBuffer SPLIT = ByteBuffer.wrap(Constants.CRLF); public static final boolean ENABLE_CACHED_STRING_BYTES = Boolean .valueOf(System.getProperty( "xmemcached.string.bytes.cached.enable", "false")); /** * if it is testing,check key argument even if use binary protocol. The user * must never change this value at all. */ public static boolean testing; private ByteUtils() { } public static boolean isValidString(String s) { return s != null && s.trim().length() > 0; } public static boolean isNumber(String string) { if (string == null || string.isEmpty()) { return false; } int i = 0; if (string.charAt(0) == '-') { if (string.length() > 1) { i++; } else { return false; } } for (; i < string.length(); i++) { if (!Character.isDigit(string.charAt(i))) { return false; } } return true; } public static final byte[] getBytes(String k) { if (k == null || k.length() == 0) { throw new IllegalArgumentException("Key must not be blank"); } if (ENABLE_CACHED_STRING_BYTES) { return CachedString.getBytes(k); } try { return k.getBytes(DEFAULT_CHARSET_NAME); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } public static final void setArguments(IoBuffer bb, Object... args) { boolean wasFirst = true; for (Object o : args) { if (wasFirst) { wasFirst = false; } else { bb.put(Constants.SPACE); } if (o instanceof byte[]) { bb.put((byte[]) o); } else { bb.put(getBytes(String.valueOf(o))); } } bb.put(Constants.CRLF); } public static final int setArguments(byte[] bb, int index, Object... args) { boolean wasFirst = true; int s = index; for (Object o : args) { if (wasFirst) { wasFirst = false; } else { bb[s++] = Constants.SPACE; } if (o instanceof byte[]) { byte[] tmp = (byte[]) o; System.arraycopy(tmp, 0, bb, s, tmp.length); s += tmp.length; } else if (o instanceof Integer) { int v = ((Integer) o).intValue(); s += stringSize(v); getBytes(v, s, bb); } else if (o instanceof String) { byte[] tmp = getBytes((String) o); System.arraycopy(tmp, 0, bb, s, tmp.length); s += tmp.length; } else if (o instanceof Long) { long v = ((Long) o).longValue(); s += stringSize(v); getBytes(v, s, bb); } } System.arraycopy(Constants.CRLF, 0, bb, s, 2); s += 2; return s; } public static final void checkKey(final byte[] keyBytes) { if (keyBytes.length > ByteUtils.maxKeyLength) { throw new IllegalArgumentException("Key is too long (maxlen = " + ByteUtils.maxKeyLength + ")"); } // Validate the key if (memcachedProtocol == Protocol.Text || testing) { for (byte b : keyBytes) { if (b == ' ' || b == '\n' || b == '\r' || b == 0) { try { throw new IllegalArgumentException( "Key contains invalid characters: " + new String(keyBytes, "utf-8")); } catch (UnsupportedEncodingException e) { } } } } } public static final void checkKey(final String key) { if (key == null || key.length() == 0) { throw new IllegalArgumentException("Key must not be blank"); } byte[] keyBytes = getBytes(key); if (keyBytes.length > ByteUtils.maxKeyLength) { throw new IllegalArgumentException("Key is too long (maxlen = " + ByteUtils.maxKeyLength + ")"); } if (memcachedProtocol == Protocol.Text || testing) { // Validate the key for (byte b : keyBytes) { if (b == ' ' || b == '\n' || b == '\r' || b == 0) { try { throw new IllegalArgumentException( "Key contains invalid characters:" + new String(keyBytes, "utf-8")); } catch (UnsupportedEncodingException e) { } } } } } private static Protocol memcachedProtocol = Protocol.Text; private static int maxKeyLength = 250; public static void setProtocol(Protocol protocol) { if (protocol == null) { throw new NullPointerException("Null Protocol"); } memcachedProtocol = protocol; // if (protocol == Protocol.Text) { // maxKeyLength = 250; // } // else { // maxKeyLength = 65535; // } } public static final int normalizeCapacity(int requestedCapacity) { switch (requestedCapacity) { case 0: case 1 << 0: case 1 << 1: case 1 << 2: case 1 << 3: case 1 << 4: case 1 << 5: case 1 << 6: case 1 << 7: case 1 << 8: case 1 << 9: case 1 << 10: case 1 << 11: case 1 << 12: case 1 << 13: case 1 << 14: case 1 << 15: case 1 << 16: case 1 << 17: case 1 << 18: case 1 << 19: case 1 << 21: case 1 << 22: case 1 << 23: case 1 << 24: case 1 << 25: case 1 << 26: case 1 << 27: case 1 << 28: case 1 << 29: case 1 << 30: case Integer.MAX_VALUE: return requestedCapacity; } int newCapacity = 1; while (newCapacity < requestedCapacity) { newCapacity <<= 1; if (newCapacity < 0) { return Integer.MAX_VALUE; } } return newCapacity; } public static final boolean stepBuffer(ByteBuffer buffer, int remaining) { if (buffer.remaining() >= remaining) { buffer.position(buffer.position() + remaining); return true; } else { return false; } } /** * Read next line from ByteBuffer * * @param buffer * @return */ public static final String nextLine(ByteBuffer buffer) { if (buffer == null) { return null; } int index = MemcachedDecoder.SPLIT_MATCHER .matchFirst(com.google.code.yanf4j.buffer.IoBuffer.wrap(buffer)); if (index >= 0) { int limit = buffer.limit(); buffer.limit(index); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); buffer.limit(limit); buffer.position(index + ByteUtils.SPLIT.remaining()); return getString(bytes); } return null; } public static String getString(byte[] bytes) { try { return new String(bytes, DEFAULT_CHARSET_NAME); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } public static void byte2hex(byte b, StringBuffer buf) { char[] hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; int high = ((b & 0xf0) >> 4); int low = (b & 0x0f); buf.append(hexChars[high]); buf.append(hexChars[low]); } public static void int2hex(int a, StringBuffer str) { str.append(Integer.toHexString(a)); } public static void short2hex(int a, StringBuffer str) { str.append(Integer.toHexString(a)); } public static void getBytes(long i, int index, byte[] buf) { long q; int r; int pos = index; byte sign = 0; if (i < 0) { sign = '-'; i = -i; } // Get 2 digits/iteration using longs until quotient fits into an int while (i > Integer.MAX_VALUE) { q = i / 100; // really: r = i - (q * 100); r = (int) (i - ((q << 6) + (q << 5) + (q << 2))); i = q; buf[--pos] = DigitOnes[r]; buf[--pos] = DigitTens[r]; } // Get 2 digits/iteration using ints int q2; int i2 = (int) i; while (i2 >= 65536) { q2 = i2 / 100; // really: r = i2 - (q * 100); r = i2 - ((q2 << 6) + (q2 << 5) + (q2 << 2)); i2 = q2; buf[--pos] = DigitOnes[r]; buf[--pos] = DigitTens[r]; } // Fall thru to fast mode for smaller numbers // assert(i2 <= 65536, i2); for (;;) { q2 = (i2 * 52429) >>> (16 + 3); r = i2 - ((q2 << 3) + (q2 << 1)); // r = i2-(q2*10) ... buf[--pos] = digits[r]; i2 = q2; if (i2 == 0) break; } if (sign != 0) { buf[--pos] = sign; } } /** * Places characters representing the integer i into the character array * buf. The characters are placed into the buffer backwards starting with * the least significant digit at the specified index (exclusive), and * working backwards from there. * * Will fail if i == Integer.MIN_VALUE */ static void getBytes(int i, int index, byte[] buf) { int q, r; int pos = index; byte sign = 0; if (i < 0) { sign = '-'; i = -i; } // Generate two digits per iteration while (i >= 65536) { q = i / 100; // really: r = i - (q * 100); r = i - ((q << 6) + (q << 5) + (q << 2)); i = q; buf[--pos] = DigitOnes[r]; buf[--pos] = DigitTens[r]; } // Fall thru to fast mode for smaller numbers // assert(i <= 65536, i); for (;;) { q = (i * 52429) >>> (16 + 3); r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ... buf[--pos] = digits[r]; i = q; if (i == 0) break; } if (sign != 0) { buf[--pos] = sign; } } /** * All possible chars for representing a number as a String */ final static byte[] digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; final static byte[] DigitTens = { '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', '9', '9', '9', '9', '9', '9', '9', '9', '9', '9', }; final static byte[] DigitOnes = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', }; final static int[] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, Integer.MAX_VALUE }; // Requires positive x public static final int stringSize(int x) { for (int i = 0;; i++) if (x <= sizeTable[i]) return i + 1; } // Requires positive x public static final int stringSize(long x) { long p = 10; for (int i = 1; i < 19; i++) { if (x < p) return i; p = 10 * p; } return 19; } final static int[] byte_len_array = new int[256]; static { for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE; ++i) { int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i); byte_len_array[i & 0xFF] = size; } } public static byte int3(int x) { return (byte) (x >> 24); } public static byte int2(int x) { return (byte) (x >> 16); } public static byte int1(int x) { return (byte) (x >> 8); } public static byte int0(int x) { return (byte) (x); } public static byte short1(short x) { return (byte) (x >> 8); } public static byte short0(short x) { return (byte) (x); } public static byte long7(long x) { return (byte) (x >> 56); } public static byte long6(long x) { return (byte) (x >> 48); } public static byte long5(long x) { return (byte) (x >> 40); } public static byte long4(long x) { return (byte) (x >> 32); } public static byte long3(long x) { return (byte) (x >> 24); } public static byte long2(long x) { return (byte) (x >> 16); } public static byte long1(long x) { return (byte) (x >> 8); } public static byte long0(long x) { return (byte) (x); } }