Code example for ByteBuffer

Methods: array, arrayOffset, limit, position, remaining

0
 
    // used externally  
    public Map<String, Object> decodeByteBuffer(ByteBuffer buffer) throws IOException {
        InputStream is = new BDecoderInputStreamArray(buffer);
        Map<String, Object> result = decode(is);
        buffer.position(buffer.limit() - is.available());
        return result;
    } 
 
    @SuppressWarnings("unchecked") 
    public Map<String, Object> decodeStream(BufferedInputStream data) throws IOException {
        Object res = decodeInputStream(data, "", 0);
 
        if (res == null) {
 
            throw (new BEncodingException("BDecoder: zero length file")); 
 
        } else if (!(res instanceof Map)) {
 
            throw (new BEncodingException("BDecoder: top level isn't a Map")); 
        } 
 
        return (Map<String, Object>) res;
    } 
 
    @SuppressWarnings("unchecked") 
    private Map<String, Object> decode(InputStream data) throws IOException {
        Object res = decodeInputStream(data, "", 0);
 
        if (res == null) {
 
            throw (new BEncodingException("BDecoder: zero length file")); 
 
        } else if (!(res instanceof Map)) {
 
            throw (new BEncodingException("BDecoder: top level isn't a Map")); 
        } 
 
        return ((Map<String, Object>) res);
    } 
 
    // reuseable objects for key decoding 
    private ByteBuffer keyBytesBuffer = ByteBuffer.allocate(32);
    private CharBuffer keyCharsBuffer = CharBuffer.allocate(32);
    private CharsetDecoder keyDecoder = Constants.BYTE_CHARSET.newDecoder();
 
    private Object decodeInputStream(InputStream dbis, String context, int nesting) throws IOException {
        if (nesting == 0 && !dbis.markSupported()) {
            throw new IOException("InputStream must support the mark() method");
        } 
 
        //set a mark 
 
        dbis.mark(Integer.MAX_VALUE);
 
        //read a byte 
 
        int tempByte = dbis.read();
 
        //decide what to do 
 
        switch (tempByte) {
        case 'd': 
            //create a new dictionary object 
 
            HashMap<String, Object> tempMap = new HashMap<String, Object>();
 
            try { 
                byte[] prev_key = null;
 
                //get the key    
 
                while (true) { 
 
                    dbis.mark(Integer.MAX_VALUE);
 
                    tempByte = dbis.read();
                    if (tempByte == 'e' || tempByte == -1)
                        break; // end of map 
 
                    dbis.reset();
 
                    // decode key strings manually so we can reuse the bytebuffer 
 
                    int keyLength = (int) getPositiveNumberFromStream(dbis, ':');
 
                    if (keyLength > MAX_MAP_KEY_SIZE) {
                        byte[] remaining = new byte[128];
                        getByteArrayFromStream(dbis, 128, remaining);
                        String msg = "dictionary key is too large, max=" + MAX_MAP_KEY_SIZE + ": value=" + new String(remaining);
                        System.err.println(msg);
                        throw (new IOException(msg));
                    } 
 
                    if (keyLength < keyBytesBuffer.capacity()) {
                        keyBytesBuffer.position(0).limit(keyLength);
                        keyCharsBuffer.position(0).limit(keyLength);
                    } else { 
                        keyBytesBuffer = ByteBuffer.allocate(keyLength);
                        keyCharsBuffer = CharBuffer.allocate(keyLength);
                    } 
 
                    getByteArrayFromStream(dbis, keyLength, keyBytesBuffer.array());
 
                    if (verify_map_order) {
 
                        byte[] current_key = new byte[keyLength];
 
                        System.arraycopy(keyBytesBuffer.array(), 0, current_key, 0, keyLength);
 
                        if (prev_key != null) {
 
                            int len = Math.min(prev_key.length, keyLength);
 
                            int state = 0;
 
                            for (int i = 0; i < len; i++) {
 
                                int cb = current_key[i] & 0x00ff;
                                int pb = prev_key[i] & 0x00ff;
 
                                if (cb > pb) {
                                    state = 1;
                                    break; 
                                } else if (cb < pb) {
                                    state = 2;
                                    break; 
                                } 
                            } 
 
                            if (state == 0) {
                                if (prev_key.length > keyLength) {
 
                                    state = 2;
                                } 
                            } 
 
                            if (state == 2) {
 
                                // Debug.out( "Dictionary order incorrect: prev=" + new String( prev_key ) + ", current=" + new String( current_key )); 
 
                                if (!(tempMap instanceof HashMapEx)) {
 
                                    HashMapEx x = new HashMapEx(tempMap);
 
                                    x.setFlag(HashMapEx.FL_MAP_ORDER_INCORRECT, true);
 
                                    tempMap = x;
                                } 
                            } 
                        } 
 
                        prev_key = current_key;
                    } 
 
                    keyDecoder.reset();
                    keyDecoder.decode(keyBytesBuffer, keyCharsBuffer, true);
                    keyDecoder.flush(keyCharsBuffer);
                    String key = new String(keyCharsBuffer.array(), 0, keyCharsBuffer.limit());
 
                    // keys often repeat a lot - intern to save space 
                    //					if (internKeys) 
                    //						key = StringInterner.intern( key ); 
 
                    //decode value 
 
                    Object value = decodeInputStream(dbis, key, nesting + 1);
 
                    // value interning is too CPU-intensive, let's skip that for now 
                    /*if(value instanceof byte[] && ((byte[])value).length < 17) 
                    value = StringInterner.internBytes((byte[])value);*/ 
 
                    // recover from some borked encodings that I have seen whereby the value has 
                    // not been encoded. This results in, for example,  
                    // 18:azureus_propertiesd0:e 
                    // we only get null back here if decoding has hit an 'e' or end-of-file 
                    // that is, there is no valid way for us to get a null 'value' here 
 
                    if (value == null) {
 
                        System.err.println("Invalid encoding - value not serialsied for '" + key + "' - ignoring");
 
                        break; 
                    } 
 
                    if (tempMap.put(key, value) != null) {
 
                        Debug.out("BDecoder: key '" + key + "' already exists!");
                    } 
                } 
 
                /*	 
                if ( tempMap.size() < 8 ){ 
 
                tempMap = new CompactMap( tempMap ); 
                }*/ 
 
                dbis.mark(Integer.MAX_VALUE);
                tempByte = dbis.read();
                dbis.reset();
                if (nesting > 0 && tempByte == -1) {
 
                    throw (new BEncodingException("BDecoder: invalid input data, 'e' missing from end of dictionary")); 
                } 
            } catch (Throwable e) {
 
                if (!recovery_mode) {
 
                    if (e instanceof IOException) {
 
                        throw ((IOException) e);
                    } 
 
                    throw (new IOException(Debug.getNestedExceptionMessage(e)));
                } 
            } 
 
            //tempMap.compactify(-0.9f); 
 
            //return the map 
 
            return tempMap;
 
        case 'l': 
            //create the list 
 
            ArrayList<Object> tempList = new ArrayList<Object>();
 
            try { 
                //create the key 
 
                String context2 = context;
 
                Object tempElement = null;
                while ((tempElement = decodeInputStream(dbis, context2, nesting + 1)) != null) {
                    //add the element 
                    tempList.add(tempElement);
                } 
 
                tempList.trimToSize();
                dbis.mark(Integer.MAX_VALUE);
                tempByte = dbis.read();
                dbis.reset();
                if (nesting > 0 && tempByte == -1) {
 
                    throw (new BEncodingException("BDecoder: invalid input data, 'e' missing from end of list")); 
                } 
            } catch (Throwable e) {
 
                if (!recovery_mode) {
 
                    if (e instanceof IOException) {
 
                        throw ((IOException) e);
                    } 
 
                    throw (new IOException(Debug.getNestedExceptionMessage(e)));
                } 
            } 
            //return the list 
            return tempList;
 
        case 'e': 
        case -1: 
            return null; 
 
        case 'i': 
            return Long.valueOf(getNumberFromStream(dbis, 'e'));
 
        case '0': 
        case '1': 
        case '2': 
        case '3': 
        case '4': 
        case '5': 
        case '6': 
        case '7': 
        case '8': 
        case '9': 
            //move back one 
            dbis.reset();
            //get the string 
            return getByteArrayFromStream(dbis, context);
 
        default: { 
 
            int rem_len = dbis.available();
 
            if (rem_len > 256) {
 
                rem_len = 256;
            } 
 
            byte[] rem_data = new byte[rem_len];
 
            dbis.read(rem_data);
 
            throw (new BEncodingException("BDecoder: unknown command '" + tempByte + ", remainder = " + new String(rem_data)));
        } 
        } 
    } 
 
    /** only create the array once per decoder instance (no issues with recursion as it's only used in a leaf method) 
     */ 
    private final char[] numberChars = new char[32];
 
    /** 
     * @note will break (likely return a negative) if number > 
     * {@link Integer#MAX_VALUE}.  This check is intentionally skipped to 
     * increase performance 
     */ 
    private int getPositiveNumberFromStream(InputStream dbis, char parseChar) throws IOException {
        int tempByte = dbis.read();
        if (tempByte < 0) {
            return -1; 
        } 
        if (tempByte != parseChar) {
 
            int value = tempByte - '0';
 
            tempByte = dbis.read();
            // optimized for single digit cases 
            if (tempByte == parseChar) {
                return value;
            } 
            if (tempByte < 0) {
                return -1; 
            } 
 
            while (true) { 
                // Base10 shift left --> v*8 + v*2 = v*10 
                value = (value << 3) + (value << 1) + (tempByte - '0');
                // For bounds check: 
                // if (value < 0) return something; 
                tempByte = dbis.read();
                if (tempByte == parseChar) {
                    return value;
                } 
                if (tempByte < 0) {
                    return -1; 
                } 
            } 
        } else { 
            return 0; 
        } 
    } 
 
    private long getNumberFromStream(InputStream dbis, char parseChar) throws IOException {
 
        int tempByte = dbis.read();
 
        int pos = 0;
 
        while ((tempByte != parseChar) && (tempByte >= 0)) {
            numberChars[pos++] = (char) tempByte;
            if (pos == numberChars.length) {
                throw (new NumberFormatException("Number too large: " + new String(numberChars, 0, pos) + "..."));
            } 
            tempByte = dbis.read();
        } 
 
        //are we at the end of the stream? 
 
        if (tempByte < 0) {
 
            return -1; 
 
        } else if (pos == 0) {
            // support some borked impls that sometimes don't bother encoding anything 
 
            return (0); 
        } 
 
        try { 
            return (parseLong(numberChars, 0, pos));
 
        } catch (NumberFormatException e) {
 
            String temp = new String(numberChars, 0, pos);
 
            try { 
                double d = Double.parseDouble(temp);
 
                long l = (long) d;
 
                Debug.out("Invalid number '" + temp + "' - decoding as " + l + " and attempting recovery");
 
                return (l);
 
            } catch (Throwable f) {
            } 
 
            throw (e);
        } 
    } 
 
    // This is similar to Long.parseLong(String) source 
    // It is also used in projects external to azureus2/azureus3 hence it is public 
    public static long parseLong(char[] chars, int start, int length) {
        if (length > 0) {
            // Short Circuit: We don't support octal parsing, so if it  
            // starts with 0, it's 0 
            if (chars[start] == '0') {
 
                return 0; 
            } 
 
            long result = 0;
 
            boolean negative = false;
 
            int i = start;
 
            long limit;
 
            if (chars[i] == '-') {
 
                negative = true;
 
                limit = Long.MIN_VALUE;
 
                i++;
 
            } else { 
                // Short Circuit: If we are only processing one char, 
                // and it wasn't a '-', just return that digit instead 
                // of doing the negative junk 
                if (length == 1) {
                    int digit = chars[i] - '0';
 
                    if (digit < 0 || digit > 9) {
 
                        throw new NumberFormatException(new String(chars, start, length));
 
                    } else { 
 
                        return digit;
                    } 
                } 
 
                limit = -Long.MAX_VALUE;
            } 
 
            int max = start + length;
 
            if (i < max) {
 
                int digit = chars[i++] - '0';
 
                if (digit < 0 || digit > 9) {
 
                    throw new NumberFormatException(new String(chars, start, length));
 
                } else { 
 
                    result = -digit;
                } 
            } 
 
            long multmin = limit / 10;
 
            while (i < max) {
 
                // Accumulating negatively avoids surprises near MAX_VALUE 
 
                int digit = chars[i++] - '0';
 
                if (digit < 0 || digit > 9) {
 
                    throw new NumberFormatException(new String(chars, start, length));
                } 
 
                if (result < multmin) {
 
                    throw new NumberFormatException(new String(chars, start, length));
                } 
 
                result *= 10;
 
                if (result < limit + digit) {
 
                    throw new NumberFormatException(new String(chars, start, length));
                } 
 
                result -= digit;
            } 
 
            if (negative) {
 
                if (i > start + 1) {
 
                    return result;
 
                } else { /* Only got "-" */ 
 
                    throw new NumberFormatException(new String(chars, start, length));
                } 
            } else { 
 
                return -result;
            } 
        } else { 
 
            throw new NumberFormatException(new String(chars, start, length));
        } 
 
    } 
 
    private byte[] getByteArrayFromStream(InputStream dbis, String context) throws IOException {
        int length = (int) getPositiveNumberFromStream(dbis, ':');
 
        if (length < 0) {
            return null; 
        } 
 
        // note that torrent hashes can be big (consider a 55GB file with 2MB pieces 
        // this generates a pieces hash of 1/2 meg 
 
        // aldenml: Note for android, we need to refactor this to allow fine control of memory allocation 
        if (length > MAX_BYTE_ARRAY_SIZE) {
            throw (new IOException("Byte array length too large (" + length + ")"));
        } 
 
        byte[] tempArray = new byte[length];
 
        getByteArrayFromStream(dbis, length, tempArray);
 
        return tempArray;
    } 
 
    private void getByteArrayFromStream(InputStream dbis, int length, byte[] targetArray) throws IOException {
 
        int count = 0;
        int len = 0;
        //get the string 
        while (count != length && (len = dbis.read(targetArray, count, length - count)) > 0)
            count += len;
 
        if (count != length)
            throw (new IOException("BDecoder::getByteArrayFromStream: truncated"));
    } 
 
    public void setVerifyMapOrder(boolean b) {
        verify_map_order = b;
    } 
 
    public void setRecoveryMode(boolean r) {
        recovery_mode = r;
    } 
 
    public static void print(Object obj) {
        StringWriter sw = new StringWriter();
 
        PrintWriter pw = new PrintWriter(sw);
 
        print(pw, obj);
 
        pw.flush();
 
        System.out.println(sw.toString());
    } 
 
    public static void print(PrintWriter writer, Object obj) {
        print(writer, obj, "", false);
    } 
 
    private static void print(PrintWriter writer, Object obj, String indent, boolean skip_indent) {
        String use_indent = skip_indent ? "" : indent;
 
        if (obj instanceof Long) {
 
            writer.println(use_indent + obj);
 
        } else if (obj instanceof byte[]) {
 
            byte[] b = (byte[]) obj;
 
            if (b.length == 20) {
                writer.println(use_indent + " { " + ByteFormatter.nicePrint(b) + " }");
            } else if (b.length < 64) {
                writer.println(new String(b) + " [" + ByteFormatter.encodeString(b) + "]");
            } else { 
                writer.println("[byte array length " + b.length);
            } 
 
        } else if (obj instanceof String) {
 
            writer.println(use_indent + obj);
 
        } else if (obj instanceof List) {
 
            @SuppressWarnings("unchecked") 
            List<Object> l = (List<Object>) obj;
 
            writer.println(use_indent + "[");
 
            for (int i = 0; i < l.size(); i++) {
 
                writer.print(indent + "  (" + i + ") ");
 
                print(writer, l.get(i), indent + "    ", true);
            } 
 
            writer.println(indent + "]");
 
        } else { 
 
            @SuppressWarnings("unchecked") 
            Map<String, Object> m = (Map<String, Object>) obj;
 
            Iterator<String> it = m.keySet().iterator();
 
            while (it.hasNext()) {
 
                String key = (String) it.next();
 
                if (key.length() > 256) {
                    writer.print(indent + key.substring(0, 256) + "... = ");
                } else { 
                    writer.print(indent + key + " = ");
                } 
 
                print(writer, m.get(key), indent + "  ", true);
            } 
        } 
    } 
 
    /** 
     * Converts any byte[] entries into UTF-8 strings. 
     * REPLACES EXISTING MAP VALUES 
     *  
     * @param map 
     * @return 
     */ 
 
    @SuppressWarnings("unchecked") 
    public static Map<String, Object> decodeStrings(Map<String, Object> map) {
        if (map == null) {
 
            return (null); 
        } 
 
        Iterator<Map.Entry<String, Object>> it = map.entrySet().iterator();
 
        while (it.hasNext()) {
 
            Map.Entry<String, Object> entry = (Map.Entry<String, Object>) it.next();
 
            Object value = entry.getValue();
 
            if (value instanceof byte[]) {
 
                try { 
                    entry.setValue(new String((byte[]) value, "UTF-8"));
 
                } catch (Throwable e) {
 
                    System.err.println(e);
                } 
            } else if (value instanceof Map) {
 
                decodeStrings((Map<String, Object>) value);
            } else if (value instanceof List) {
 
                decodeStrings((List<Object>) value);
            } 
        } 
 
        return (map);
    } 
 
    /** 
     * Decodes byte arrays into strings.   
     * REPLACES EXISTING LIST VALUES 
     *  
     * @param list 
     * @return the same list passed in 
     */ 
    @SuppressWarnings("unchecked") 
    public static List<Object> decodeStrings(List<Object> list) {
        if (list == null) {
 
            return (null); 
        } 
 
        for (int i = 0; i < list.size(); i++) {
 
            Object value = list.get(i);
 
            if (value instanceof byte[]) {
 
                try { 
                    String str = new String((byte[]) value, "UTF-8");
 
                    list.set(i, str);
 
                } catch (Throwable e) {
 
                    System.err.println(e);
                } 
            } else if (value instanceof Map) {
 
                decodeStrings((Map<String, Object>) value);
 
            } else if (value instanceof List) {
 
                decodeStrings((List<Object>) value);
            } 
        } 
 
        return (list);
    } 
 
    private class BDecoderInputStreamArray extends InputStream {
        final private byte[] bytes;
        private int pos = 0;
        private int markPos;
        private int overPos;
 
        public BDecoderInputStreamArray(ByteBuffer buffer) {
            bytes = buffer.array();
            pos = buffer.arrayOffset() + buffer.position();
            overPos = pos + buffer.remaining();
        } 
 
        private BDecoderInputStreamArray(byte[] _buffer) {
            bytes = _buffer;
            overPos = bytes.length;