001    package org.otfeed.support;
002    
003    import java.nio.ByteBuffer;
004    
005    
006    
007    /**
008     * Converts ByteBuffer type to/from a string.
009     * <p/>
010     * Formats buffer into multi-line string, each line
011     * showing up to 16 bytes of the data in a standard "hexdump"
012     * format. For example,
013     * <pre>
014     * 00000001 40360000 00000000 21000001     %....@6......!...
015     * 148a6fe0 69                             %..o.i
016     * </pre>
017     * <p/>
018     * When parsing, ignores empty lines, and treat separator 
019     * symbol as start of comment. Therefore, following is a valid
020     * input to the parse method:
021     * <pre>
022     * %
023     * % buffer snippet
024     * %
025     * 00000001 40360000 00000000 21000001     %....@6......!...
026     * 148a6fe0 69                             %..o.i
027     * </pre>
028     * <p/>
029     */
030    public class BufferFormat implements IFormat<ByteBuffer> {
031            
032            private final char DEFAULT_SEPARATOR = '%';
033            
034            private final IBufferAllocator allocator;
035            
036            /**
037             * Creates format using the provided allocator service
038             * (used for {@link #parse(String) parsing} only.
039             * 
040             * @param allocator allocator.
041             */
042            public BufferFormat(IBufferAllocator allocator) {
043                    this.allocator = allocator;
044            }
045            
046            /**
047             * Creates format using the default 
048             * {@link ByteReverseBufferAllocator byte-reversed allocation service}.
049             */
050            public BufferFormat() {
051                    this(new ByteReverseBufferAllocator());
052            }
053            
054            private char separator = DEFAULT_SEPARATOR;
055            
056            /**
057             * Symbol that marks the beginnig of comment.
058             * 
059             * @return separator character.
060             */
061            public char getSeparator() {
062                    return separator;
063            }
064            
065            /**
066             * Sets separator character.
067             * 
068             * @param val separator character.
069             */
070            public void setSeparator(char val) {
071                    separator = val;
072            }
073            
074            private static final String PRINTABLES 
075                    = "~`!@#$%^&*()_+-=[]{}\\|,.<>/?;:'\"";
076    
077            private static char translateToPrintable(int val) {
078                    if((val >= 'a' && val <= 'z') 
079                                    || (val >= 'A' && val <= 'Z')
080                                    || Character.isDigit(val)
081                                    || PRINTABLES.indexOf((char) val) >= 0) {
082                            return (char) val;
083                    } else {
084                            return '.';
085                    }
086            }
087            
088            private static final String HEX = "0123456789abcdef";
089    
090            public String format(ByteBuffer buffer) {
091                    StringBuilder builder = new StringBuilder();
092                    StringBuilder verbose = new StringBuilder();
093                    
094                    int length = buffer.limit() - buffer.position();
095                    for(int i = 0; i < length; i++) {
096                            int val = buffer.get(buffer.position() + i);
097                            if(val < 0) val += 256; // unsigned!
098                            
099                            verbose.append(translateToPrintable(val));
100                            
101                            builder.append(HEX.charAt(val >> 4));
102                            builder.append(HEX.charAt(val & 0xf));
103                            if((i % 16) == 15) {
104                                    builder.append("\t");
105                                    builder.append(separator);
106                                    builder.append(verbose);
107                                    builder.append("\n");
108                                    verbose.setLength(0);
109                            } else if((i % 4) == 3) {
110                                    builder.append(" ");
111                            }
112                    }
113                    
114                    int padding = length % 16;
115                    if(padding > 0) {
116                            while(padding < 16) {
117                                    
118                                    builder.append("  ");
119                                    
120                                    if((padding % 16) == 15) {
121                                            builder.append("\t");
122                                            builder.append(separator);
123                                            builder.append(verbose);
124                                            builder.append("\n");
125                                    } else if((padding % 4) == 3) {
126                                            builder.append(" ");
127                                    }
128                                    
129                                    padding++;
130                            }
131                    }
132                    
133                    return builder.toString();
134            }
135    
136            public ByteBuffer parse(String val) {
137                    
138                    ByteBuffer buffer = allocator.allocate(val.length() / 2);
139                    String [] lines = val.split("\n");
140                    for(int i = 0; i < lines.length; i++) {
141                            String line = lines[i];
142                            int index = line.indexOf(separator);
143                            if(index >= 0) line = line.substring(0, index).trim();
144                            
145                            line = line.replaceAll("\\s", "").toLowerCase();
146                            if(line.length() == 0) {
147                                    // ignore comments and empty lines
148                                    continue;
149                            }
150                            
151                            int size = line.length() / 2;
152                            if(line.length() != size * 2) {
153                                    throw new IllegalArgumentException("can't parse line " + (i + 1)
154                                                    + " of the buffer: odd number of nibbles");
155                            }
156                            
157                            for(int j = 0; j < size; j++) {
158                                    int high = line.charAt(j * 2);
159                                    int low  = line.charAt(j * 2 + 1);
160                                    
161                                    high = HEX.indexOf((char) high);
162                                    low  = HEX.indexOf((char) low);
163                                    
164                                    if(high < 0 || low < 0) {
165                                            throw new IllegalArgumentException("not a hex number: " 
166                                                            + line + ", position=" + (j * 2));
167                                    }
168                                    
169                                    buffer.put((byte)((high << 4) + low));
170                            }
171                    }
172                    
173                    buffer.flip();
174                    
175                    return buffer;
176            }
177    
178    }