View Javadoc

1   package net.obsearch.index;
2   
3   /*
4    OBSearch: a distributed similarity search engine This project is to
5    similarity search what 'bit-torrent' is to downloads. 
6    Copyright (C) 2008 Arnoldo Jose Muller Molina
7   
8    This program is free software: you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10   the Free Software Foundation, either version 3 of the License, or
11   (at your option) any later version.
12  
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17  
18   You should have received a copy of the GNU General Public License
19   along with this program.  If not, see <http://www.gnu.org/licenses/>.
20   */
21  
22  import hep.aida.bin.StaticBin1D;
23  
24  import java.io.ByteArrayInputStream;
25  import java.io.ByteArrayOutputStream;
26  import java.io.DataInputStream;
27  import java.io.DataOutputStream;
28  import java.io.File;
29  import java.io.FileWriter;
30  import java.io.IOException;
31  import java.lang.reflect.Array;
32  import java.nio.ByteBuffer;
33  import java.util.ArrayList;
34  import java.util.List;
35  import java.util.Random;
36  import java.util.logging.Logger;
37  
38  
39  import net.obsearch.Index;
40  import net.obsearch.OB;
41  import net.obsearch.OperationStatus;
42  import net.obsearch.Status;
43  import net.obsearch.asserts.OBAsserts;
44  import net.obsearch.cache.OBCacheHandlerLong;
45  import net.obsearch.cache.OBCacheLong;
46  import net.obsearch.constants.OBSearchProperties;
47  import net.obsearch.exception.AlreadyFrozenException;
48  import net.obsearch.exception.IllegalIdException;
49  import net.obsearch.exception.NotFrozenException;
50  import net.obsearch.exception.OBException;
51  import net.obsearch.exception.OBStorageException;
52  import net.obsearch.exception.OutOfRangeException;
53  import net.obsearch.exception.PivotsUnavailableException;
54  import net.obsearch.stats.Statistics;
55  import net.obsearch.storage.OBStore;
56  import net.obsearch.utils.bytes.ByteConversion;
57  
58  import net.obsearch.storage.OBStorageConfig;
59  import net.obsearch.storage.OBStoreFactory;
60  import net.obsearch.storage.OBStoreLong;
61  import net.obsearch.storage.OBStorageConfig.IndexType;
62  
63  import com.thoughtworks.xstream.XStream;
64  
65  /**
66   * AbstractOBIndex contains functionality regarding object storage. Children of
67   * this class should define a way of searching those objects. This class
68   * provides default implementations of methods that are considered optional in
69   * the Index interface.
70   * 
71   * @author Arnoldo Jose Muller Molina
72   */
73  public abstract class AbstractOBIndex<O extends OB> implements Index<O> {
74  
75  	/**
76  	 * If we should auto generate the id. true if we call first
77  	 * {@link #insert(OB)}.
78  	 */
79  	private boolean autoGenerateId = true;
80  	/**
81  	 * Used to detect the first call of an insert method.
82  	 */
83  	private boolean firstInsert = true;
84  
85  	/**
86  	 * Statistics related to this index.
87  	 */
88  	protected transient Statistics stats;
89  
90  	/**
91  	 * Objects are stored by their id's here.
92  	 */
93  	protected transient OBStoreLong A;
94  
95  	/**
96  	 * Factory used by this class and by subclasses to create appropiate storage
97  	 * devices.
98  	 */
99  	protected transient OBStoreFactory fact;
100 
101 	/**
102 	 * Required during pre-freeze to make only one copy of an object is
103 	 * inserted. (the index is not built at this stage therefore it is not
104 	 * possible to know if an object is already in the DB.
105 	 */
106 	private transient OBStore preFreeze;
107 
108 	/**
109 	 * If we should use the pre-freeze db. Use this if you don't know if the
110 	 * before-freeze data is totally unique. Setting this to false will speed up
111 	 * a lot insertion before freeze. You should turn this flag off specially if
112 	 * the data objects (OBs) are big. TODO: use an md5 sum to check if objects
113 	 * are in preFreeze. It would make things much more efficient and cheap for
114 	 * prefreeze.
115 	 */
116 	private boolean preFreezeCheck = false;
117 
118 	public boolean isPreFreezeCheck() {
119 		return preFreezeCheck;
120 	}
121 	
122 	public AbstractOBIndex(){
123 		
124 	}
125 
126 	public void setPreFreezeCheck(boolean preFreezeCheck) {
127 		this.preFreezeCheck = preFreezeCheck;
128 	}
129 
130 	/**
131 	 * Cache used for storing recently accessed objects O.
132 	 */
133 	private transient OBCacheLong<O> aCache;
134 
135 	/**
136 	 * True if this index is frozen.
137 	 */
138 	protected boolean isFrozen;
139 
140 	/**
141 	 * The type used to instantiate objects of type O.
142 	 */
143 	protected Class<O> type;
144 	
145 	
146 	/**
147 	 * Logger.
148 	 */
149 	private static final transient Logger logger = Logger
150 			.getLogger(AbstractOBIndex.class.getCanonicalName());
151 
152 	/**
153 	 * Constructors of an AbstractOBIndex should receive only parameters related
154 	 * to the operation of the index. The factory and the initialization will be
155 	 * executed by {@link #init(OBStoreFactory)}
156 	 * 
157 	 * @param type
158 	 * @throws OBStorageException
159 	 * @throws OBException
160 	 */
161 	protected AbstractOBIndex(Class<O> type) throws OBStorageException,
162 			OBException {
163 		this.type = type;
164 	}
165 	
166 	/**
167 	 * Clear A cache. 
168 	 */
169 	protected void clearACache(){
170 		aCache.clear();
171 	}
172 
173 	/**
174 	 * Returns the type of the object to be stored.
175 	 * 
176 	 * @return {@link #type}
177 	 */
178 	public final Class<O> getType() {
179 		return type;
180 	}
181 
182 	/**
183 	 * If the database is frozen returns silently if it is not throws
184 	 * NotFrozenException.
185 	 * 
186 	 * @throws NotFrozenException
187 	 *             if the index has not been frozen.
188 	 */
189 	protected void assertFrozen() throws NotFrozenException {
190 		if (!isFrozen()) {
191 			throw new NotFrozenException();
192 		}
193 	}
194 
195 	/**
196 	 * Initialize the index.
197 	 * 
198 	 * @throws OBStorageException
199 	 * @throws OBException
200 	 */
201 	public void init(OBStoreFactory fact) throws OBStorageException,
202 			OBException, NotFrozenException, IllegalAccessException,
203 			InstantiationException, OBException {
204 		this.fact = fact;
205 		initStorageDevices();
206 		initCache();
207 		stats = new Statistics();
208 	}
209 
210 	/**
211 	 * Initializes storage devices required by this class.
212 	 * 
213 	 * @throws OBStorageException
214 	 *             If the storage device could not be created.
215 	 */
216 	protected void initStorageDevices() throws OBStorageException, OBException {
217 		OBStorageConfig conf = new OBStorageConfig();
218 		conf.setTemp(false);
219 		conf.setDuplicates(false);
220 		conf.setBulkMode(! isFrozen());
221 		conf.setIndexType(IndexType.HASH);
222 		this.A = fact.createOBStoreLong("A", conf );
223 		if (!this.isFrozen()) {
224 			conf = new OBStorageConfig();
225 			conf.setTemp(false);
226 			conf.setDuplicates(false);
227 			conf.setBulkMode(false);
228 			conf.setRecordSize(fixedRecordSize);
229 			if(fixedRecord){
230 				conf.setIndexType(IndexType.FIXED_RECORD);
231 			}
232 			this.preFreeze = fact.createOBStore("pre", conf);
233 		}
234 	}
235 	
236 	private boolean fixedRecord = false;
237 	
238 	public void setFixedRecord(boolean fixedRecord){
239 		this.fixedRecord = fixedRecord;
240 	}
241 	
242 	private int fixedRecordSize = -1;
243 	
244 	public void setFixedRecord(int fixedRecordSize){
245 		this.fixedRecordSize = fixedRecordSize;
246 	}
247 
248 	/**
249 	 * Initializes the object cache {@link #aCache}.
250 	 * 
251 	 * @throws DatabaseException
252 	 *             If something goes wrong with the DB.
253 	 */
254 	protected void initCache() throws OBException {
255 		aCache = new OBCacheLong<O>(new ALoader(), OBSearchProperties
256 				.getACacheSize());
257 	}
258 
259 	/**
260 	 * This class is in charge of loading objects.
261 	 * 
262 	 * @author amuller
263 	 */
264 	private class ALoader implements OBCacheHandlerLong<O> {
265 
266 		public long getDBSize() throws OBStorageException {
267 			return A.size();
268 		}
269 
270 		public O loadObject(long i) throws OBException, InstantiationException,
271 				IllegalAccessException, IllegalIdException {
272 
273 			byte[] data = A.getValue(i);
274 			if (data == null) {
275 				throw new IllegalIdException(i);
276 			}
277 
278 			return bytesToObject(data);
279 		}
280 
281 		@Override
282 		public void store(long key, O object) throws OBException {
283 			// nothing to do, we already store A when we should.
284 		}
285 
286 	}
287 	
288 	/**
289 	 * Loads object i into the given object
290 	 * @param i The object to load.
291 	 * @param object Where we will upload the data.
292 	 * @throws IOException 
293 	 * @throws OBException 
294 	 */
295 	public void loadObject(long i, O object) throws OBException {
296 		try{
297 		byte[] data = A.getValue(i);
298 		object.load(data);
299 		}catch(IOException e){
300 			throw new OBStorageException(e);
301 		}
302 	}
303 	
304 	/**
305 	 * Calculate the intrinsic dimension of the underlying database 
306 	 * @param sample
307 	 * @return
308 	 * @throws OBException 
309 	 * @throws InstantiationException 
310 	 * @throws IllegalAccessException 
311 	 * @throws IllegalIdException 
312 	 */
313 	public double intrinsicDimensionality(int sampleSize) throws IllegalIdException, IllegalAccessException, InstantiationException, OBException{
314 		
315 		List<O> objs = new ArrayList<O>(sampleSize);
316 		Random r = new Random();
317 		long max = this.databaseSize();
318 		
319 		int i = 0;
320 		while(i < sampleSize){
321 			long id = Math.abs(r.nextLong() % max);
322 			objs.add(getObject(id));
323 			i++; 
324 		}
325 		i = 0;
326 		StaticBin1D stats = new StaticBin1D();
327 		while(i < sampleSize){
328 			int i2 = 0;
329 			O a = objs.get(i);
330 			while(i2 < sampleSize){
331 				if(i2 != i){
332 					O b = objs.get(i2);
333 					stats.add(distance(a,b));
334 				}
335 				i2++;
336 			}			
337 			logger.info("Doing: " + i);						
338 			i++;
339 		}
340 		logger.info("Distance Stats: " + stats.toString());
341 		return Math.pow(stats.mean(), 2) / (2 * stats.variance());
342 	}
343 	
344 	protected double distance(O a2, O b) throws OBException{
345 		throw new UnsupportedOperationException(); 
346 	}
347 
348 	/**
349 	 * If we are going to check for the existence of data before freezing.
350 	 * 
351 	 * @return true if we are going to check for the existence of data before
352 	 *         freezing.
353 	 */
354 	public boolean isPreFreeze() {
355 		return preFreezeCheck;
356 	}
357 
358 	/**
359 	 * If we are going to check for the existence of data before freezing
360 	 * preFreezeCheck should be set to true. If you know each object is unique then
361 	 * you can set this to false and with this improve performance.
362 	 * 
363 	 * @param preFreezeCheck
364 	 *            true (quality, data integrity) false (performance)
365 	 */
366 	public void setPreFreeze(boolean isPreFreeze) {
367 		this.preFreezeCheck = isPreFreeze;
368 	}
369 
370 	/**
371 	 * Instantiates an object O from the given data array.
372 	 */
373 	protected O bytesToObject(ByteBuffer data) throws OBException,
374 			InstantiationException, IllegalAccessException, IllegalIdException {
375 		return bytesToObject(data.array());
376 	}
377 
378 	/**
379 	 * Instantiates an object O from the given data array.
380 	 */
381 	protected O bytesToObject(byte[] data) throws OBException,
382 			InstantiationException, IllegalAccessException, IllegalIdException {
383 		O res = type.newInstance();
384 		try {
385 			res.load(data);
386 		} catch (IOException e) {
387 			throw new OBException(e);
388 		}
389 		return res;
390 	}
391 
392 	/*
393 	 * (non-Javadoc)
394 	 * 
395 	 * @see net.obsearch.result.Index#close()
396 	 */
397 	@Override
398 	public void close() throws OBException {
399 		A.close();
400 		 if (this.preFreeze != null) {
401 			 preFreeze.close();
402 		 }
403 		fact.close();
404 	}
405 
406 	/*
407 	 * (non-Javadoc)
408 	 * 
409 	 * @see net.obsearch.result.Index#databaseSize()
410 	 */
411 	@Override
412 	public long databaseSize() throws OBStorageException {
413 		return A.size();
414 	}
415 
416 	public String debug(O object) throws OBException, InstantiationException,
417 			IllegalAccessException {
418 		return object.toString();
419 	}
420 
421 	@Override
422 	public OperationStatus delete(O object) throws OBException,
423 			IllegalAccessException, InstantiationException, NotFrozenException {
424 		if (this.isFrozen()) {
425 			OperationStatus res = deleteAux(object);
426 			if (res.getStatus() == Status.OK) {
427 				this.A.delete(res.getId());
428 				assert A.getValue(res.getId()) == null;
429 			}
430 			return res;
431 		} else {
432 			throw new NotFrozenException();
433 		}
434 	}
435 
436 	/**
437 	 * Deletes the entry of this object in the index. The current class will
438 	 * remove the object from A if the result status is
439 	 * {@link net.obsearch.Status#OK}.
440 	 * 
441 	 * @param object
442 	 *            object to be deleted.
443 	 * @return {@link net.obsearch.Status#OK} if the object was deleted.
444 	 *         {@link net.obsearch.Status#NOT_EXISTS} no object matched.
445 	 */
446 	protected abstract OperationStatus deleteAux(O object) throws OBException,
447 			IllegalAccessException, InstantiationException;
448 
449 	/**
450 	 * Converts an object into an array of bytes.
451 	 * 
452 	 * @param object
453 	 *            Object to convert.
454 	 * @return The bytes array representation of the object.
455 	 */
456 	protected byte[] objectToBytes(O object) throws OBException {
457 		try {
458 			return object.store();
459 
460 		} catch (IOException e) {
461 			throw new OBException(e);
462 		}
463 	}
464 
465 	protected byte[] objectToByteBuffer(O object) throws OBException {
466 		return objectToBytes(object);
467 	}
468 
469 	/*
470 	 * (non-Javadoc)
471 	 * 
472 	 * @see net.obsearch.result.Index#getObject(long)
473 	 */
474 	@Override
475 	public O getObject(long id) throws IllegalIdException,
476 			IllegalAccessException, InstantiationException, OBException {
477 		// get the object from A, this is easy.
478 		return aCache.get(id);
479 	}
480 
481 	/**
482 	 * Find the Id of the given object. (the distance 0 is considered as equal)
483 	 * 
484 	 * @param object
485 	 *            The object to search
486 	 * @return {@link net.obsearch.Status#OK} if the object is found (with the
487 	 *         id) otherwise, {@link net.obsearch.Status#NOT_EXISTS}
488 	 * @throws IllegalIdException
489 	 * @throws IllegalAccessException
490 	 * @throws InstantiationException
491 	 * @throws OBException
492 	 */
493 	protected OperationStatus findAux(O object) throws IllegalIdException,
494 			IllegalAccessException, InstantiationException, OBException {
495 		throw new UnsupportedOperationException();
496 	}
497 
498 	/*
499 	 * (non-Javadoc)
500 	 * 
501 	 * @see net.obsearch.result.Index#exists(net.obsearch.result.OB)
502 	 */
503 
504 	/*
505 	 * public OperationStatus exists(O object) throws OBStorageException,
506 	 * OBException, IllegalAccessException, InstantiationException {
507 	 * OperationStatus t = findAux(object); if (t.getStatus() == Status.OK) {
508 	 * t.setStatus(Status.EXISTS); } return t; }
509 	 */
510 
511 	/*
512 	 * (non-Javadoc)
513 	 * 
514 	 * @see net.obsearch.result.Index#getStats()
515 	 */
516 	@Override
517 	public Statistics getStats() throws OBStorageException {
518 		//stats.putStats("Read Stats A", A.getReadStats());
519 		//stats.putObjects("Env", fact.stats());
520 		return stats;
521 	}
522 
523 	public void setIdAutoGeneration(boolean auto) throws OBException {
524 		OBAsserts.chkAssert(A.size() == 0,
525 				"Cannot change id generation if the index is not empty");
526 		autoGenerateId = auto;
527 	}
528 
529 	/*
530 	 * (non-Javadoc)
531 	 * 
532 	 * @see net.obsearch.result.Index#insert(net.obsearch.result.OB)
533 	 */
534 	@Override
535 	public OperationStatus insert(O object) throws OBStorageException,
536 			OBException, IllegalAccessException, InstantiationException {
537 		return insert(object, -1);
538 
539 	}
540 
541 	public OperationStatus insertBulk(O object) throws OBStorageException,
542 			OBException, IllegalAccessException, InstantiationException {
543 		return insertBulk(object, A.nextId());
544 	}
545 
546 	public OperationStatus insertBulk(O object, long id)
547 			throws OBStorageException, OBException, IllegalAccessException,
548 			InstantiationException {
549 
550 		OperationStatus res = new OperationStatus();
551 		res.setStatus(Status.OK);
552 
553 		// validate if the id is not in the DB.
554 		res.setId(id);
555 		if (this.isFrozen()) {
556 
557 			// must insert object into A before the index is updated
558 			OBAsserts.chkAssert(A.getValue(id) == null,
559 					"id already used, fatal error");
560 			this.A.put(id, objectToByteBuffer(object));
561 			// update the index:
562 			
563 			res = insertAux(id, object);
564 			
565 
566 		} else { // before freeze
567 					
568 			OBAsserts.chkAssert(A.getValue(id) == null,
569 					"id already used, fatal error");
570 			this.A.put(id, objectToByteBuffer(object));
571 
572 		}
573 		return res;
574 	}
575 	
576 	
577 
578 	public OperationStatus insert(O object, long id) throws OBStorageException,
579 			OBException, IllegalAccessException, InstantiationException {
580 
581 		OperationStatus res = new OperationStatus();
582 		res.setStatus(Status.OK);
583 
584 		// validate if the id is not in the DB.
585 
586 		if (this.isFrozen()) {
587 			res = exists(object);
588 			if (res.getStatus() == Status.NOT_EXISTS) {
589 				if (id == -1) { // auto mode
590 					id = A.nextId();
591 				}
592 				// must insert object into A before the index is updated
593 				OBAsserts.chkAssert( A.getValue(id) == null,
594 						"id already used, fatal error" + id);
595 				this.A.put(id, objectToByteBuffer(object));
596 				// update the index:
597 				
598 				res = insertAux(id, object);
599 				res.setId(id);
600 			}
601 
602 		} else { // before freeze
603 			// we keep track of objects that have been inserted
604 			// based on their binary signature.
605 			// TODO: maybe change this to a hash to avoid the problem
606 			// with objects that have multiplicity.
607 			if (preFreezeCheck) {
608 				byte[] key = objectToBytes(object);
609 				byte[] value = this.preFreeze.getValue(key);
610 				if (value == null) {
611 					if (id == -1) {
612 						id = A.nextId();
613 					}
614 					res.setId(id);
615 					preFreeze.put(key, ByteConversion.longToBytes(id));
616 				} else {
617 					res.setStatus(Status.EXISTS);
618 					res.setId(ByteConversion.bytesToLong(value));
619 				}
620 			} else {
621 				res.setId(id);
622 			}
623 
624 			// insert the object in A if everything is OK.
625 			if (res.getStatus() == Status.OK) {
626 				if (id == -1) {
627 					id = A.nextId();
628 					res.setId(id);
629 				}
630 				OBAsserts.chkAssert(A.getValue(id) == null,
631 						"id already used, fatal error: " + id);
632 				this.A.put(res.getId(), objectToByteBuffer(object));
633 			}
634 
635 		}
636 		return res;
637 	}
638 
639 	/**
640 	 * Inserts the given object into the particular index. The caller inserts
641 	 * the actual object so the implementing class only has to worry about
642 	 * adding the id inside the index.
643 	 * 
644 	 * @param id
645 	 *            The id that will be used to insert the object.
646 	 * @param object
647 	 *            The object that will be inserted.
648 	 * @return If {@link net.obsearch.Status#OK} or
649 	 *         {@link net.obsearch.Status#EXISTS} then the result will hold the
650 	 *         id of the inserted object and the operation is successful.
651 	 *         Otherwise an exception will be thrown.
652 	 * @throws OBStorageException
653 	 * @throws OBException
654 	 * @throws IllegalAccessException
655 	 * @throws InstantiationException
656 	 */
657 	protected abstract OperationStatus insertAux(long id, O object)
658 			throws OBStorageException, OBException, IllegalAccessException,
659 			InstantiationException;
660 
661 	/**
662 	 * Inserts the given object into the particular index. No checks regarding
663 	 * existence are performed. We assume the user already checked uniqueness.
664 	 * 
665 	 * @param id
666 	 *            The id that will be used to insert the object.
667 	 * @param object
668 	 *            The object that will be inserted.
669 	 * @return {@link net.obsearch.Status#OK}
670 	 * 
671 	 * @throws OBStorageException
672 	 * @throws OBException
673 	 * @throws IllegalAccessException
674 	 * @throws InstantiationException
675 	 */
676 	protected abstract OperationStatus insertAuxBulk(long id, O object)
677 			throws OBStorageException, OBException, IllegalAccessException,
678 			InstantiationException;
679 
680 	/**
681 	 * @throws PivotsUnavailableException 
682 	 * @throws IOException 
683 	 * @see net.obsearch.Index#freeze()
684 	 */
685 	@Override
686 	public void freeze() throws  AlreadyFrozenException,
687 			IllegalIdException, IllegalAccessException, InstantiationException,
688 			OBStorageException, OutOfRangeException, OBException, PivotsUnavailableException, IOException {
689 		if (isFrozen()) {
690 			// TODO: allow indexes to freeze multiple times.
691 			throw new AlreadyFrozenException();
692 		}
693 		this.isFrozen = true;
694 
695 	}
696 
697 	/*
698 	 * (non-Javadoc)
699 	 * 
700 	 * @see net.obsearch.result.Index#getBox(net.obsearch.result.OB)
701 	 */
702 	@Override
703 	public long getBox(O object) throws OBException {
704 		throw new UnsupportedOperationException();
705 	}
706 
707 	/*
708 	 * (non-Javadoc)
709 	 * 
710 	 * @see net.obsearch.result.Index#isFrozen()
711 	 */
712 	@Override
713 	public boolean isFrozen() {
714 		return this.isFrozen;
715 	}
716 
717 	/*
718 	 * (non-Javadoc)
719 	 * 
720 	 * @see net.obsearch.result.Index#resetStats()
721 	 */
722 	@Override
723 	public void resetStats() {
724 		stats = new Statistics();
725 	}
726 
727 	/*
728 	 * (non-Javadoc)
729 	 * 
730 	 * @see net.obsearch.result.Index#totalBoxes()
731 	 */
732 	@Override
733 	public long totalBoxes() {
734 		throw new UnsupportedOperationException();
735 	}
736 
737 	/**
738 	 * Finds the given objects in A and serializes them.
739 	 * 
740 	 * @param ids
741 	 *            Objects that will be loaded
742 	 * @return Serialization of the objects.
743 	 * @throws OBException
744 	 * @throws InstantiationException
745 	 * @throws IllegalAccessException
746 	 * @throws IllegalIdException
747 	 */
748 	protected byte[][] serializePivots(final long[] ids)
749 			throws IllegalIdException, IllegalAccessException,
750 			InstantiationException, OBException {
751 		byte[][] result = new byte[ids.length][];
752 		int i = 0;
753 		while (i < ids.length) {
754 			O obj = getObject(ids[i]);
755 			result[i] = this.objectToBytes(obj);
756 
757 			i++;
758 		}
759 		return result;
760 	}
761 
762 	/**
763 	 * Create an empty pivots array.
764 	 * 
765 	 * @return an empty pivots array of size {@link #pivotsCount}.
766 	 */
767 	public O[] emptyPivotsArray(int size) {
768 		return (O[]) Array.newInstance(this.getType(), size);
769 	}
770 
771 	protected O[] loadPivots(byte[][] serializedPivots)
772 			throws IllegalIdException, OBException, InstantiationException,
773 			IllegalAccessException {
774 		O[] result = emptyPivotsArray(serializedPivots.length);
775 		int i = 0;
776 		while (i < serializedPivots.length) {
777 			result[i] = bytesToObject(serializedPivots[i]);
778 			i++;
779 		}
780 		assert i == serializedPivots.length; // pivot count and read # of pivots
781 		return result;
782 	}
783 
784 }