001    /*
002     * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org
003     * Copyright (C) 2011 NightLabs Consulting GmbH
004     *
005     * This program is free software: you can redistribute it and/or modify
006     * it under the terms of the GNU Affero General Public License as
007     * published by the Free Software Foundation, either version 3 of the
008     * License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU Affero General Public License for more details.
014     *
015     * You should have received a copy of the GNU Affero General Public License
016     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017     */
018    package org.cumulus4j.store.model;
019    
020    import java.io.ByteArrayOutputStream;
021    import java.util.Collections;
022    import java.util.HashSet;
023    import java.util.Set;
024    
025    /**
026     * Helper for en- &amp; decoding the decrypted (plain) contents of
027     * {@link IndexEntry#getIndexValue() IndexEntry.indexValue}. This byte-array holds
028     * references to {@link DataEntry#getDataEntryID() DataEntry.dataEntryID}s.
029     *
030     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
031     */
032    public class IndexValue
033    {
034            private static final boolean OPTIMIZED_ENCODING = true;
035            private Set<Long> dataEntryIDs;
036    
037            /**
038             * Create an empty instance of <code>IndexValue</code>. This is equivalent to
039             * calling {@link #IndexValue(byte[])} with a <code>null</code> or an empty argument.
040             */
041            public IndexValue() {
042                    this(null);
043            }
044    
045            /**
046             * Create an <code>IndexValue</code> instance from the decrypted (plain) byte-array
047             * which is stored in {@link IndexEntry#getIndexValue() IndexEntry.indexValue}.
048             *
049             * @param indexValueByteArray the plain (decrypted) byte-array of {@link IndexEntry#getIndexValue()} or <code>null</code>
050             * (<code>null</code> is equivalent to an empty byte-array). This byte-array is what is created by {@link #toByteArray()}.
051             */
052            public IndexValue(byte[] indexValueByteArray) {
053                    if (indexValueByteArray == null)
054                            dataEntryIDs = new HashSet<Long>(); // A HashSet is faster than a TreeSet and I don't see a need for the sorting.
055                    else {
056                            if (OPTIMIZED_ENCODING) {
057    //                              dataEntryIDs = new HashSet<Long>(); // A HashSet is faster than a TreeSet and I don't see a need for the sorting.
058                                    dataEntryIDs = new HashSet<Long>(indexValueByteArray.length / 4);
059                                    if (indexValueByteArray.length > 0) {
060                                            // The first byte (index 0) is the version. Currently only version 1 is supported.
061                                            int version = (indexValueByteArray[0] & 0xff);
062                                            switch (version) {
063                                                    case 1: {
064                                                            for (int i = 1; i < indexValueByteArray.length; ++i) {
065                                                                    // take the first 3 bits and shift them to the right; add 1 (because we have 1 to 8 following - not 0 to 7)
066                                                                    final int bytesFollowing = (7 & (indexValueByteArray[i] >>> 5)) + 1;
067                                                                    // take all but the first 3 bits (if 8 bytes follow, we don't need this, because these 8 bytes are a full long, already).
068                                                                    final int payloadFromFirstByte = bytesFollowing == 8 ? 0 : (indexValueByteArray[i] & 0x1f);
069                                                                    long dataEntryID = payloadFromFirstByte;
070                                                                    for (int n = 0; n < bytesFollowing; ++n) {
071                                                                            dataEntryID = (dataEntryID << 8) | (indexValueByteArray[++i] & 0xff);
072                                                                    }
073                                                                    dataEntryIDs.add(dataEntryID);
074                                                            }
075                                                            break;
076                                                    }
077                                                    default:
078                                                            throw new IllegalArgumentException("Unsupported version: " + version);
079                                            }
080                                    }
081                            }
082                            else {
083                                    if ((indexValueByteArray.length % 8) != 0)
084                                            throw new IllegalArgumentException("indexValueByteArray.length is not dividable by 8!");
085    
086                                    dataEntryIDs = new HashSet<Long>(indexValueByteArray.length / 8);
087    
088                                    for (int i = 0; i < indexValueByteArray.length / 8; ++i) {
089                                            long dataEntryID =
090                                                            (((long)indexValueByteArray[i * 8 + 0] & 0xff) << 56) +
091                                                            (((long)indexValueByteArray[i * 8 + 1] & 0xff) << 48) +
092                                                            (((long)indexValueByteArray[i * 8 + 2] & 0xff) << 40) +
093                                                            (((long)indexValueByteArray[i * 8 + 3] & 0xff) << 32) +
094                                                            (((long)indexValueByteArray[i * 8 + 4] & 0xff) << 24) +
095                                                            (((long)indexValueByteArray[i * 8 + 5] & 0xff) << 16) +
096                                                            (((long)indexValueByteArray[i * 8 + 6] & 0xff) <<  8) +
097                                                            (indexValueByteArray[i * 8 + 7] & 0xff)
098                                                            ;
099                                            dataEntryIDs.add(dataEntryID);
100                                    }
101                            }
102                    }
103            }
104    
105            /**
106             * Get a byte-array with all {@link #getDataEntryIDs() dataEntryIDs}. It can be passed to
107             * {@link #IndexValue(byte[])} later (e.g. after encrypting, persisting, loading &amp; decrypting).
108             * @return a byte-array holding all dataEntryIDs managed by this instance.
109             */
110            public byte[] toByteArray()
111            {
112                    if (OPTIMIZED_ENCODING) {
113                            ByteArrayOutputStream out = new ByteArrayOutputStream(dataEntryIDs.size() * 8);
114                            // version 1
115                            out.write(1);
116    
117                            // write dataEntryIDs
118                            for (long dataEntryID : dataEntryIDs) {
119                                    if (dataEntryID < 0)
120                                            throw new IllegalStateException("dataEntryID < 0!!!");
121    
122    // first implementation (replaced by a slightly faster one below)
123    //                              byte[] va = new byte[8];
124    //                              int firstNonNullIndex = -1;
125    //                              int m = 8;
126    //                              for (int n = 0; n < 8; ++n) {
127    //                                      --m;
128    //                                      va[n] = (byte)(dataEntryID >>> (8 * m));
129    //                                      if (firstNonNullIndex < 0 && va[n] != 0)
130    //                                              firstNonNullIndex = n;
131    //                              }
132    //                              if (firstNonNullIndex < 0)
133    //                                      firstNonNullIndex = 7;
134    //
135    //                              int idx;
136    //                              int firstByte;
137    //                              if (firstNonNullIndex < 7 && (0xff & va[firstNonNullIndex]) <= 0x1f) {
138    //                                      // Merge first non-null byte with meta-byte.
139    //                                      idx = firstNonNullIndex + 1;
140    //                                      firstByte = va[firstNonNullIndex];
141    //                              }
142    //                              else {
143    //                                      // Value too high or minimum of 1 following byte.
144    //                                      // NOT merge first non-null byte with meta-byte!
145    //                                      idx = firstNonNullIndex;
146    //                                      firstByte = 0;
147    //                              }
148    //
149    //                              int bytesFollowingMinus1 = 7 - idx;
150    //                              firstByte |= bytesFollowingMinus1 << 5;
151    //                              out.write(firstByte);
152    //                              while (idx < 8) {
153    //                                      out.write(va[idx++]);
154    //                              }
155    
156                                    // slightly faster implementation (harder to read, though)
157                                    int firstNonNullIndex = -1;
158                                    int m = 8;
159                                    for (int n = 0; n < 8; ++n) {
160                                            --m;
161                                            int v = ((byte)(dataEntryID >>> (8 * m))) & 0xff;
162                                            if (firstNonNullIndex >= 0 || v != 0 || n == 7) {
163                                                    if (firstNonNullIndex < 0 || (firstNonNullIndex < 0 && n == 7)) {
164                                                            firstNonNullIndex = n;
165                                                            // Meta-byte was not yet written - handle size header now.
166    
167                                                            int bytesFollowingMinus1 = 7 - firstNonNullIndex;
168                                                            if (firstNonNullIndex < 7 && v <= 0x1f) {
169                                                                    --bytesFollowingMinus1;
170                                                                    // Merge first non-null byte with meta-byte.
171                                                                    v |= bytesFollowingMinus1 << 5;
172                                                                    out.write(v);
173                                                            }
174                                                            else {
175                                                                    // Value too high or minimum of 1 following byte.
176                                                                    // NOT merge first non-null byte with meta-byte!
177                                                                    out.write(bytesFollowingMinus1 << 5);
178                                                                    out.write(v);
179                                                            }
180                                                    }
181                                                    else {
182                                                            // Meta-byte was already written - simply write payload
183                                                            out.write(v);
184                                                    }
185                                            }
186                                    }
187    
188                            }
189                            return out.toByteArray();
190                    }
191                    else {
192                            byte[] result = new byte[dataEntryIDs.size() * 8];
193                            int i = -1;
194                            for (Long dataEntryID : dataEntryIDs) {
195                                    long v = dataEntryID;
196                                    result[++i] = (byte)(v >>> 56);
197                                    result[++i] = (byte)(v >>> 48);
198                                    result[++i] = (byte)(v >>> 40);
199                                    result[++i] = (byte)(v >>> 32);
200                                    result[++i] = (byte)(v >>> 24);
201                                    result[++i] = (byte)(v >>> 16);
202                                    result[++i] = (byte)(v >>> 8);
203                                    result[++i] = (byte)v;
204                            }
205                            return result;
206                    }
207            }
208    
209            /**
210             * Get {@link DataEntry#getDataEntryID() dataEntryID}s referencing those {@link DataEntry}s which this <code>IndexValue</code>
211             * (or more precisely the {@link IndexEntry} from which this <code>IndexValue</code> was created) points to.
212             * @return the object-IDs of the <code>DataEntry</code> instances that are referenced by this index entry.
213             */
214            public Set<Long> getDataEntryIDs() {
215                    return Collections.unmodifiableSet(dataEntryIDs);
216            }
217    
218            public boolean isDataEntryIDsEmpty()
219            {
220                    return dataEntryIDs.isEmpty();
221            }
222    
223            public boolean addDataEntryID(long dataEntryID)
224            {
225                    return dataEntryIDs.add(dataEntryID);
226            }
227    
228            public boolean removeDataEntryID(long dataEntryID)
229            {
230                    return dataEntryIDs.remove(dataEntryID);
231            }
232    
233            @Override
234            public int hashCode() {
235                    return dataEntryIDs.hashCode();
236            }
237    
238            @Override
239            public boolean equals(Object obj) {
240                    if (this == obj) return true;
241                    if (obj == null) return false;
242                    if (getClass() != obj.getClass()) return false;
243                    IndexValue other = (IndexValue) obj;
244                    return this.dataEntryIDs.equals(other.dataEntryIDs);
245            }
246    
247    //      public static void main(String[] args) {
248    //              Random random = new Random();
249    //              IndexValue indexValue1 = new IndexValue();
250    //              for (int i = 0; i < 100; ++i) {
251    //                      long dataEntryID = random.nextLong();
252    //                      indexValue1.addDataEntryID(dataEntryID);
253    //              }
254    //
255    //              for (Long dataEntryID : indexValue1.getDataEntryIDs()) {
256    //                      System.out.println(dataEntryID);
257    //              }
258    //
259    //              System.out.println();
260    //              System.out.println();
261    //              System.out.println();
262    //
263    //              byte[] byteArray = indexValue1.toByteArray();
264    //
265    //              IndexValue indexValue2 = new IndexValue(byteArray);
266    //              for (Long dataEntryID : indexValue2.getDataEntryIDs()) {
267    //                      System.out.println(dataEntryID);
268    //              }
269    //
270    //              System.out.println();
271    //              System.out.println();
272    //              System.out.println();
273    //
274    //              System.out.println(indexValue1.equals(indexValue2));
275    //      }
276    
277    }