001    /**
002     * Copyright (c) 2000-2011 Liferay, Inc. All rights reserved.
003     *
004     * The contents of this file are subject to the terms of the Liferay Enterprise
005     * Subscription License ("License"). You may not use this file except in
006     * compliance with the License. You can obtain a copy of the License by
007     * contacting Liferay, Inc. See the License for the specific language governing
008     * permissions and limitations under the License, including but not limited to
009     * distribution rights of the Software.
010     *
011     *
012     *
013     */
014    
015    package com.liferay.portal.kernel.io.delta;
016    
017    import java.io.IOException;
018    
019    import java.nio.ByteBuffer;
020    import java.nio.channels.ReadableByteChannel;
021    
022    import java.security.MessageDigest;
023    
024    import java.util.HashMap;
025    import java.util.Map;
026    
027    /**
028     * @author Connor McKay
029     */
030    public class Differ {
031    
032            public void delta(
033                            ReadableByteChannel modifiedReadableByteChannel,
034                            ByteChannelReader checksumsByteChannelReader,
035                            ByteChannelWriter deltaByteChannelWriter)
036                    throws IOException {
037    
038                    _modifiedReadableByteChannel = modifiedReadableByteChannel;
039                    _checksumsByteChannelReader = checksumsByteChannelReader;
040                    _deltaByteChannelWriter = deltaByteChannelWriter;
041    
042                    _checksumsByteChannelReader.resizeBuffer(
043                            DeltaUtil.BUFFER_FACTOR * 20);
044    
045                    _checksumsByteBuffer = _checksumsByteChannelReader.getBuffer();
046    
047                    readChecksumsHeader();
048                    readChecksums();
049    
050                    _rollingChecksum = new RollingChecksum(
051                            _modifiedReadableByteChannel, _blockLength);
052    
053                    _deltaByteChannelWriter.resizeBuffer(
054                            _blockLength * DeltaUtil.BUFFER_FACTOR + 5);
055    
056                    _deltaByteBuffer = _deltaByteChannelWriter.getBuffer();
057    
058                    if ((_dataByteBuffer == null) ||
059                            (_dataByteBuffer.capacity() <
060                                    (_blockLength * DeltaUtil.BUFFER_FACTOR))) {
061    
062                            _dataByteBuffer = ByteBuffer.allocate(
063                                    _blockLength * DeltaUtil.BUFFER_FACTOR);
064                    }
065    
066                    writeDeltaHeader();
067                    writeDeltaBlocks();
068            }
069    
070            protected void readChecksums() throws IOException {
071                    _blockDatas = new HashMap<Integer, BlockData>(_blocksCount);
072    
073                    for (int blockNumber = 0; blockNumber < _blocksCount; blockNumber++) {
074                            _checksumsByteChannelReader.ensureData(20);
075    
076                            int weakChecksum = _checksumsByteBuffer.getInt();
077    
078                            byte[] strongChecksum = new byte[16];
079    
080                            _checksumsByteBuffer.get(strongChecksum);
081    
082                            // It is possible that there are two or more blocks with the same
083                            // weak checksum, in which case the map will only contain the strong
084                            // checksum of the last one. In some cases, this may cause a data
085                            // block to be sent when a reference block could have been sent,
086                            // but it doesn't really matter.
087    
088                            _blockDatas.put(
089                                    weakChecksum, new BlockData(blockNumber, strongChecksum));
090                    }
091            }
092    
093            protected void readChecksumsHeader() throws IOException {
094                    _checksumsByteChannelReader.ensureData(9);
095    
096                    if (DeltaUtil.PROTOCOL_VERSION != _checksumsByteBuffer.get()) {
097                            throw new IOException("Unknown protocol version");
098                    }
099    
100                    _blockLength = _checksumsByteBuffer.getInt();
101                    _blocksCount = _checksumsByteBuffer.getInt();
102            }
103    
104            protected void writeDataBlock() throws IOException {
105                    if (_dataByteBuffer.position() == 0) {
106    
107                            // There's nothing in the data buffer
108    
109                            return;
110                    }
111    
112                    _deltaByteChannelWriter.ensureSpace(_dataByteBuffer.position() + 5);
113    
114                    _deltaByteBuffer.put(DeltaUtil.DATA_KEY);
115                    _deltaByteBuffer.putInt(_dataByteBuffer.position());
116    
117                    _dataByteBuffer.flip();
118    
119                    _deltaByteBuffer.put(_dataByteBuffer);
120    
121                    _dataByteBuffer.clear();
122            }
123    
124            protected void writeDeltaBlocks() throws IOException {
125                    _firstBlockNumber = -1;
126                    _lastBlockNumber = -1;
127    
128                    while (_rollingChecksum.hasNext()) {
129                            BlockData blockData = _blockDatas.get(
130                                    _rollingChecksum.weakChecksum());
131    
132                            if ((blockData != null) &&
133                                    MessageDigest.isEqual(
134                                            blockData.getStrongChecksum(),
135                                            _rollingChecksum.strongChecksum())) {
136    
137                                    int blockNumber = blockData.getBlockNumber();
138    
139                                    if (_firstBlockNumber == -1) {
140                                            writeDataBlock();
141    
142                                            _firstBlockNumber = blockNumber;
143                                            _lastBlockNumber = blockNumber;
144                                    }
145                                    else if (_lastBlockNumber + 1 == blockNumber) {
146    
147                                            // The blocks must be sequential in a reference range block
148    
149                                            _lastBlockNumber = blockNumber;
150                                    }
151                                    else {
152                                            writeReferenceBlock();
153    
154                                            _firstBlockNumber = blockNumber;
155                                            _lastBlockNumber = blockNumber;
156                                    }
157    
158                                    _rollingChecksum.nextBlock();
159                            }
160                            else {
161                                    writeReferenceBlock();
162    
163                                    if (!_dataByteBuffer.hasRemaining()) {
164                                            writeDataBlock();
165                                    }
166    
167                                    _dataByteBuffer.put(_rollingChecksum.getFirstByte());
168    
169                                    _rollingChecksum.nextByte();
170                            }
171                    }
172    
173                    // Only one of these should ever actually do something, but it's simpler
174                    // to call them both than choose between them.
175    
176                    writeReferenceBlock();
177                    writeDataBlock();
178    
179                    _deltaByteChannelWriter.ensureSpace(1);
180    
181                    _deltaByteBuffer.put(DeltaUtil.EOF_KEY);
182            }
183    
184            protected void writeDeltaHeader() throws IOException {
185                    _deltaByteChannelWriter.ensureSpace(5);
186    
187                    _deltaByteBuffer.put(DeltaUtil.PROTOCOL_VERSION);
188                    _deltaByteBuffer.putInt(_blockLength);
189            }
190    
191            protected void writeReferenceBlock() throws IOException {
192                    if (_firstBlockNumber == -1) {
193                            return;
194                    }
195    
196                    if (_lastBlockNumber == _firstBlockNumber) {
197                            _deltaByteChannelWriter.ensureSpace(5);
198    
199                            _deltaByteBuffer.put(DeltaUtil.REFERENCE_KEY);
200                            _deltaByteBuffer.putInt(_firstBlockNumber);
201                    }
202                    else {
203                            _deltaByteChannelWriter.ensureSpace(9);
204    
205                            _deltaByteBuffer.put(DeltaUtil.REFERENCE_RANGE_KEY);
206                            _deltaByteBuffer.putInt(_firstBlockNumber);
207                            _deltaByteBuffer.putInt(_lastBlockNumber);
208                    }
209    
210                    _firstBlockNumber = -1;
211                    _lastBlockNumber = -1;
212            }
213    
214            private Map<Integer, BlockData> _blockDatas;
215            private int _blockLength;
216            private int _blocksCount;
217            private ByteBuffer _checksumsByteBuffer;
218            private ByteChannelReader _checksumsByteChannelReader;
219            private ByteBuffer _dataByteBuffer;
220            private ByteBuffer _deltaByteBuffer;
221            private ByteChannelWriter _deltaByteChannelWriter;
222            private int _firstBlockNumber;
223            private int _lastBlockNumber;
224            private ReadableByteChannel _modifiedReadableByteChannel;
225            private RollingChecksum _rollingChecksum;
226    
227            private class BlockData {
228    
229                    public BlockData(int blockNumber, byte[] strongChecksum) {
230                            _blockNumber = blockNumber;
231                            _strongChecksum = strongChecksum;
232                    }
233    
234                    public int getBlockNumber() {
235                            return _blockNumber;
236                    }
237    
238                    public byte[] getStrongChecksum() {
239                            return _strongChecksum;
240                    }
241    
242                    private int _blockNumber;
243                    private byte[] _strongChecksum;
244    
245            }
246    
247    }