View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors. 
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   *
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.connector.store.jpa.util;
25  
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.ObjectInputStream;
29  import java.io.ObjectOutputStream;
30  import java.math.BigDecimal;
31  import java.net.URI;
32  import java.security.NoSuchAlgorithmException;
33  import java.util.Collection;
34  import java.util.HashMap;
35  import java.util.HashSet;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.UUID;
39  import org.modeshape.common.SystemFailureException;
40  import org.modeshape.common.util.SecureHash;
41  import org.modeshape.connector.store.jpa.model.simple.LargeValueEntity;
42  import org.modeshape.graph.ExecutionContext;
43  import org.modeshape.graph.ModeShapeLexicon;
44  import org.modeshape.graph.property.Binary;
45  import org.modeshape.graph.property.BinaryFactory;
46  import org.modeshape.graph.property.DateTime;
47  import org.modeshape.graph.property.Name;
48  import org.modeshape.graph.property.Path;
49  import org.modeshape.graph.property.Property;
50  import org.modeshape.graph.property.PropertyFactory;
51  import org.modeshape.graph.property.PropertyType;
52  import org.modeshape.graph.property.Reference;
53  import org.modeshape.graph.property.ReferenceFactory;
54  import org.modeshape.graph.property.UuidFactory;
55  import org.modeshape.graph.property.ValueFactories;
56  import org.modeshape.graph.property.ValueFormatException;
57  
58  /**
59   * A class that is responsible for serializing and deserializing properties.
60   */
61  public class Serializer {
62  
63      public static final LargeValues NO_LARGE_VALUES = new NoLargeValues();
64      public static final ReferenceValues NO_REFERENCES_VALUES = new NoReferenceValues();
65  
66      private final PropertyFactory propertyFactory;
67      private final ValueFactories valueFactories;
68      private final boolean excludeUuidProperty;
69  
70      public Serializer( ExecutionContext context,
71                         boolean excludeUuidProperty ) {
72          this.propertyFactory = context.getPropertyFactory();
73          this.valueFactories = context.getValueFactories();
74          this.excludeUuidProperty = excludeUuidProperty;
75      }
76  
77      /**
78       * Interface that represents the location where "large" objects are stored.
79       * 
80       * @author Randall Hauch
81       */
82      public interface LargeValues {
83          /**
84           * Get the minimum size for large values, specified as {@link String#length() number of characters} for a {@link String}
85           * or the {@link Binary#getSize() number of bytes for a binary value}
86           * 
87           * @return the size at which a property value is considered to be <i>large</i>
88           */
89          long getMinimumSize();
90  
91          void write( byte[] hash,
92                      long length,
93                      PropertyType type,
94                      Object value ) throws IOException;
95  
96          Object read( ValueFactories valueFactories,
97                       byte[] hash,
98                       long length ) throws IOException;
99      }
100 
101     protected static class NoLargeValues implements LargeValues {
102         public long getMinimumSize() {
103             return Long.MAX_VALUE;
104         }
105 
106         public void write( byte[] hash,
107                            long length,
108                            PropertyType type,
109                            Object value ) {
110         }
111 
112         public Object read( ValueFactories valueFactories,
113                             byte[] hash,
114                             long length ) {
115             return null;
116         }
117     }
118 
119     /**
120      * Interface used to record how Reference values are processed during serialization and deserialization.
121      * 
122      * @author Randall Hauch
123      */
124     public interface ReferenceValues {
125         void read( Reference reference );
126 
127         void write( Reference reference );
128 
129         void remove( Reference reference );
130     }
131 
132     protected static class NoReferenceValues implements ReferenceValues {
133         public void read( Reference arg0 ) {
134         }
135 
136         public void remove( Reference arg0 ) {
137         }
138 
139         public void write( Reference arg0 ) {
140         }
141     }
142 
143     /**
144      * Serialize the properties' values to the object stream.
145      * <p>
146      * If any of the property values are considered {@link LargeValues#getMinimumSize() large}, the value's hash and length of the
147      * property value will be written to the object stream, but the property value will be sent to the supplied
148      * {@link LargeValueEntity} object.
149      * </p>
150      * <p>
151      * This method does not automatically write each property value to the stream using
152      * {@link ObjectOutputStream#writeObject(Object)}, but instead serializes the primitive values that make up the property value
153      * object with a code that describes the {@link PropertyType property's type}. This is more efficient, since most of the
154      * property values are really non-primitive objects, and writing to the stream using
155      * {@link ObjectOutputStream#writeObject(Object)} would include larger class metadata.
156      * </p>
157      * 
158      * @param stream the stream where the properties' values are to be serialized; may not be null
159      * @param number the number of properties exposed by the supplied <code>properties</code> iterator; must be 0 or positive
160      * @param properties the iterator over the properties that are to be serialized; may not be null
161      * @param largeValues the interface to use for writing large values; may not be null
162      * @param references the interface to use for recording which {@link Reference} values were found during serialization, or
163      *        null if the references do not need to be accumulated
164      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
165      * @see #deserializeAllProperties(ObjectInputStream, Collection, LargeValues)
166      * @see #deserializeSomeProperties(ObjectInputStream, Collection, LargeValues, LargeValues, Name...)
167      * @see #serializeProperty(ObjectOutputStream, Property, LargeValues, ReferenceValues)
168      */
169     public void serializeProperties( ObjectOutputStream stream,
170                                      int number,
171                                      Iterable<Property> properties,
172                                      LargeValues largeValues,
173                                      ReferenceValues references ) throws IOException {
174         assert number >= 0;
175         assert properties != null;
176         assert largeValues != null;
177         stream.writeInt(number);
178         for (Property property : properties) {
179             if (property == null) continue;
180             serializeProperty(stream, property, largeValues, references);
181         }
182     }
183 
184     /**
185      * Serialize the property's values to the object stream.
186      * <p>
187      * If any of the property values are considered {@link LargeValues#getMinimumSize() large}, the value's hash and length of the
188      * property value will be written to the object stream, but the property value will be sent to the supplied
189      * {@link LargeValueEntity} object.
190      * </p>
191      * <p>
192      * This method does not automatically write each property value to the stream using
193      * {@link ObjectOutputStream#writeObject(Object)}, but instead serializes the primitive values that make up the property value
194      * object with a code that describes the {@link PropertyType property's type}. This is more efficient, since most of the
195      * property values are really non-primitive objects, and writing to the stream using
196      * {@link ObjectOutputStream#writeObject(Object)} would include larger class metadata.
197      * </p>
198      * 
199      * @param stream the stream where the property's values are to be serialized; may not be null
200      * @param property the property to be serialized; may not be null
201      * @param largeValues the interface to use for writing large values; may not be null
202      * @param references the interface to use for recording which {@link Reference} values were found during serialization, or
203      *        null if the references do not need to be accumulated
204      * @return true if the property was serialized, or false if it was not
205      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
206      * @see #serializeProperties(ObjectOutputStream, int, Iterable, LargeValues, ReferenceValues)
207      * @see #deserializePropertyValues(ObjectInputStream, Name, boolean, LargeValues, LargeValues, ReferenceValues)
208      */
209     public boolean serializeProperty( ObjectOutputStream stream,
210                                       Property property,
211                                       LargeValues largeValues,
212                                       ReferenceValues references ) throws IOException {
213         assert stream != null;
214         assert property != null;
215         assert largeValues != null;
216         assert references != null;
217         final Name name = property.getName();
218         if (this.excludeUuidProperty && ModeShapeLexicon.UUID.equals(name)) return false;
219         // Write the name ...
220         stream.writeObject(name.getString(Path.NO_OP_ENCODER));
221         // Write the number of values ...
222         stream.writeInt(property.size());
223         for (Object value : property) {
224             if (value instanceof String) {
225                 String stringValue = (String)value;
226                 if (largeValues != null && stringValue.length() > largeValues.getMinimumSize()) {
227                     // Store the value in the large values area, but record the hash and length here.
228                     byte[] hash = computeHash(stringValue);
229                     stream.writeChar('L');
230                     stream.writeInt(hash.length);
231                     stream.write(hash);
232                     stream.writeLong(stringValue.length());
233                     // Now write to the large objects ...
234                     largeValues.write(computeHash(stringValue), stringValue.length(), PropertyType.STRING, stringValue);
235                 } else {
236                     stream.writeChar('S');
237                     stream.writeObject(stringValue);
238                 }
239             } else if (value instanceof Boolean) {
240                 stream.writeChar('b');
241                 stream.writeBoolean(((Boolean)value).booleanValue());
242             } else if (value instanceof Long) {
243                 stream.writeChar('l');
244                 stream.writeLong(((Long)value).longValue());
245             } else if (value instanceof Double) {
246                 stream.writeChar('d');
247                 stream.writeDouble(((Double)value).doubleValue());
248             } else if (value instanceof Integer) {
249                 stream.writeChar('i');
250                 stream.writeInt(((Integer)value).intValue());
251             } else if (value instanceof Short) {
252                 stream.writeChar('s');
253                 stream.writeShort(((Short)value).shortValue());
254             } else if (value instanceof Float) {
255                 stream.writeChar('f');
256                 stream.writeFloat(((Float)value).floatValue());
257             } else if (value instanceof UUID) {
258                 stream.writeChar('U');
259                 UUID uuid = (UUID)value;
260                 stream.writeLong(uuid.getMostSignificantBits());
261                 stream.writeLong(uuid.getLeastSignificantBits());
262             } else if (value instanceof URI) {
263                 URI uri = (URI)value;
264                 String stringValue = uri.toString();
265                 if (largeValues != null && stringValue.length() > largeValues.getMinimumSize()) {
266                     // Store the URI in the large values area, but record the hash and length here.
267                     byte[] hash = computeHash(stringValue);
268                     stream.writeChar('L');
269                     stream.writeInt(hash.length);
270                     stream.write(hash);
271                     stream.writeLong(stringValue.length());
272                     // Now write to the large objects ...
273                     largeValues.write(computeHash(stringValue), stringValue.length(), PropertyType.URI, stringValue);
274                 } else {
275                     stream.writeChar('I');
276                     stream.writeObject(stringValue);
277                 }
278             } else if (value instanceof Name) {
279                 stream.writeChar('N');
280                 stream.writeObject(((Name)value).getString(Path.NO_OP_ENCODER));
281             } else if (value instanceof Path) {
282                 stream.writeChar('P');
283                 stream.writeObject(((Path)value).getString());
284             } else if (value instanceof DateTime) {
285                 stream.writeChar('T');
286                 stream.writeObject(((DateTime)value).getString());
287             } else if (value instanceof BigDecimal) {
288                 stream.writeChar('D');
289                 stream.writeObject(value);
290             } else if (value instanceof Character) {
291                 stream.writeChar('c');
292                 char c = ((Character)value).charValue();
293                 stream.writeChar(c);
294             } else if (value instanceof Reference) {
295                 Reference ref = (Reference)value;
296                 if (ref.isWeak()) {
297                     stream.writeChar('W');
298                 } else {
299                     stream.writeChar('R');
300                 }
301                 stream.writeObject(ref.getString());
302                 references.write(ref);
303             } else if (value instanceof Binary) {
304                 Binary binary = (Binary)value;
305                 byte[] hash = null;
306                 long length = 0;
307                 try {
308                     binary.acquire();
309                     length = binary.getSize();
310                     if (largeValues != null && length > largeValues.getMinimumSize()) {
311                         // Store the value in the large values area, but record the hash and length here.
312                         hash = binary.getHash();
313                         stream.writeChar('L');
314                         stream.writeInt(hash.length);
315                         stream.write(hash);
316                         stream.writeLong(length);
317                         // Write to large objects after releasing the binary
318                     } else {
319                         // The value is small enough to store here ...
320                         stream.writeChar('B');
321                         stream.writeLong(length);
322                         InputStream data = binary.getStream();
323                         try {
324                             byte[] buffer = new byte[1024];
325                             int numRead = 0;
326                             while ((numRead = data.read(buffer)) > -1) {
327                                 stream.write(buffer, 0, numRead);
328                             }
329                         } finally {
330                             data.close();
331                         }
332                     }
333                 } finally {
334                     binary.release();
335                 }
336                 // If this is a large value and the binary has been released, write it to the large objects ...
337                 if (largeValues != null && hash != null) {
338                     largeValues.write(hash, length, PropertyType.BINARY, value);
339                 }
340             } else {
341                 // Other kinds of values ...
342                 stream.writeChar('O');
343                 stream.writeObject(value);
344             }
345         }
346         stream.flush();
347         return true;
348     }
349 
350     /**
351      * Deserialize the existing properties from the supplied input stream, update the properties, and then serialize the updated
352      * properties to the output stream.
353      * 
354      * @param input the stream from which the existing properties are to be deserialized; may not be null
355      * @param output the stream to which the updated properties are to be serialized; may not be null
356      * @param updatedProperties the properties that are being updated (or removed, if there are no values); may not be null
357      * @param largeValues the interface to use for writing large values; may not be null
358      * @param removedLargeValues the interface to use for recording the large values that were removed; may not be null
359      * @param createdProperties the set into which should be placed the names of the properties that were created; may not be null
360      * @param references the interface to use for recording which {@link Reference} values were found during serialization, or
361      *        null if the references do not need to be accumulated
362      * @return the number of properties
363      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
364      * @throws ClassNotFoundException if the class for the value's object could not be found
365      */
366     public int reserializeProperties( ObjectInputStream input,
367                                       ObjectOutputStream output,
368                                       Map<Name, Property> updatedProperties,
369                                       LargeValues largeValues,
370                                       LargeValues removedLargeValues,
371                                       Set<Name> createdProperties,
372                                       ReferenceValues references ) throws IOException, ClassNotFoundException {
373         assert input != null;
374         assert output != null;
375         assert updatedProperties != null;
376         assert createdProperties != null;
377         assert largeValues != null;
378         assert references != null;
379         // Assemble a set of property names to skip deserializing
380         Map<Name, Property> allProperties = new HashMap<Name, Property>();
381 
382         // Start out by assuming that all properties are new ...
383         createdProperties.addAll(updatedProperties.keySet());
384 
385         // Read the number of properties ...
386         int count = input.readInt();
387         // Deserialize all of the properties ...
388         for (int i = 0; i != count; ++i) {
389             // Read the property name ...
390             String nameStr = (String)input.readObject();
391             Name name = valueFactories.getNameFactory().create(nameStr);
392             assert name != null;
393             if (updatedProperties.containsKey(name)) {
394                 // Deserialized, but don't materialize ...
395                 deserializePropertyValues(input, name, true, largeValues, removedLargeValues, references);
396             } else {
397                 // Now read the property values ...
398                 Object[] values = deserializePropertyValues(input, name, false, largeValues, removedLargeValues, references);
399                 // Add the property to the collection ...
400                 Property property = propertyFactory.create(name, values);
401                 assert property != null;
402                 allProperties.put(name, property);
403             }
404             // This is an existing property, so remove it from the set of created properties ...
405             createdProperties.remove(name);
406         }
407 
408         // Add all the updated properties ...
409         for (Map.Entry<Name, Property> entry : updatedProperties.entrySet()) {
410             Property updated = entry.getValue();
411             Name name = entry.getKey();
412             if (updated == null) {
413                 allProperties.remove(name);
414             } else {
415                 allProperties.put(name, updated);
416             }
417         }
418 
419         // Serialize properties ...
420         int numProperties = allProperties.size();
421         output.writeInt(numProperties);
422         for (Property property : allProperties.values()) {
423             if (property == null) continue;
424             serializeProperty(output, property, largeValues, references);
425         }
426         return numProperties;
427     }
428 
429     /**
430      * Deserialize the properties, adjust all {@link Reference} values that point to an "old" UUID to point to the corresponding
431      * "new" UUID, and reserialize the properties. If any reference is to a UUID not in the map, it is left untouched.
432      * <p>
433      * This is an efficient method that (for the most part) reads from the input stream and directly writes to the output stream.
434      * The exception is when a Reference value is read, that Reference is attempted to be remapped to a new Reference and written
435      * in place of the old reference. (Of course, if the Reference is to a UUID that is not in the "old" to "new" map, the old is
436      * written directly.)
437      * </p>
438      * 
439      * @param input the stream from which the existing properties are to be deserialized; may not be null
440      * @param output the stream to which the updated properties are to be serialized; may not be null
441      * @param oldUuidToNewUuid the map of old-to-new UUIDs; may not be null
442      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
443      * @throws ClassNotFoundException if the class for the value's object could not be found
444      */
445     public void adjustReferenceProperties( ObjectInputStream input,
446                                            ObjectOutputStream output,
447                                            Map<String, String> oldUuidToNewUuid ) throws IOException, ClassNotFoundException {
448         assert input != null;
449         assert output != null;
450         assert oldUuidToNewUuid != null;
451 
452         UuidFactory uuidFactory = valueFactories.getUuidFactory();
453 
454         // Read the number of properties ...
455         int count = input.readInt();
456         output.writeInt(count);
457         // Deserialize all of the proeprties ...
458         for (int i = 0; i != count; ++i) {
459             // Read and write the property name ...
460             Object name = input.readObject();
461             output.writeObject(name);
462             // Read and write the number of values ...
463             int numValues = input.readInt();
464             output.writeInt(numValues);
465             // Now read and write each property value ...
466             for (int j = 0; j != numValues; ++j) {
467                 // Read and write the type of value ...
468                 char type = input.readChar();
469                 output.writeChar(type);
470                 switch (type) {
471                     case 'S':
472                         output.writeObject(input.readObject());
473                         break;
474                     case 'b':
475                         output.writeBoolean(input.readBoolean());
476                         break;
477                     case 'i':
478                         output.writeInt(input.readInt());
479                         break;
480                     case 'l':
481                         output.writeLong(input.readLong());
482                         break;
483                     case 's':
484                         output.writeShort(input.readShort());
485                         break;
486                     case 'f':
487                         output.writeFloat(input.readFloat());
488                         break;
489                     case 'd':
490                         output.writeDouble(input.readDouble());
491                         break;
492                     case 'c':
493                         // char
494                         output.writeChar(input.readChar());
495                         break;
496                     case 'U':
497                         // UUID
498                         output.writeLong(input.readLong());
499                         output.writeLong(input.readLong());
500                         break;
501                     case 'I':
502                         // URI
503                         output.writeObject(input.readObject());
504                         break;
505                     case 'N':
506                         // Name
507                         output.writeObject(input.readObject());
508                         break;
509                     case 'P':
510                         // Path
511                         output.writeObject(input.readObject());
512                         break;
513                     case 'T':
514                         // DateTime
515                         output.writeObject(input.readObject());
516                         break;
517                     case 'D':
518                         // BigDecimal
519                         output.writeObject(input.readObject());
520                         break;
521                     case 'R':
522                         // Reference
523                         String refValue = (String)input.readObject();
524                         ReferenceFactory referenceFactory = valueFactories.getReferenceFactory();
525                         Reference ref = referenceFactory.create(refValue);
526                         try {
527                             UUID toUuid = uuidFactory.create(ref);
528                             String newUuid = oldUuidToNewUuid.get(toUuid.toString());
529                             if (newUuid != null) {
530                                 // Create a new reference ...
531                                 ref = referenceFactory.create(newUuid);
532                                 refValue = ref.getString();
533                             }
534                         } catch (ValueFormatException e) {
535                             // Unknown reference, so simply write it again ...
536                         }
537                         // Write the reference ...
538                         output.writeObject(refValue);
539                         break;
540                     case 'W':
541                         // Weak Reference
542                         refValue = (String)input.readObject();
543                         referenceFactory = valueFactories.getWeakReferenceFactory();
544                         ref = referenceFactory.create(refValue);
545                         try {
546                             UUID toUuid = uuidFactory.create(ref);
547                             String newUuid = oldUuidToNewUuid.get(toUuid.toString());
548                             if (newUuid != null) {
549                                 // Create a new reference ...
550                                 ref = referenceFactory.create(newUuid);
551                                 refValue = ref.getString();
552                             }
553                         } catch (ValueFormatException e) {
554                             // Unknown reference, so simply write it again ...
555                         }
556                         // Write the reference ...
557                         output.writeObject(refValue);
558                         break;
559                     case 'B':
560                         // Binary
561                         // Read the length of the content ...
562                         long binaryLength = input.readLong();
563                         byte[] content = new byte[(int)binaryLength];
564                         input.read(content);
565                         // Now write out the value ...
566                         output.writeLong(binaryLength);
567                         output.write(content);
568                         break;
569                     case 'L':
570                         // Large object ...
571                         int hashLength = input.readInt();
572                         byte[] hash = new byte[hashLength];
573                         input.read(hash);
574                         long length = input.readLong();
575                         // write to the output ...
576                         output.writeInt(hash.length);
577                         output.write(hash);
578                         output.writeLong(length);
579                         break;
580                     default:
581                         // All other objects ...
582                         output.writeObject(input.readObject());
583                         break;
584                 }
585             }
586         }
587     }
588 
589     /**
590      * Deserialize the serialized properties on the supplied object stream.
591      * 
592      * @param stream the stream that contains the serialized properties; may not be null
593      * @param properties the collection into which each deserialized property is to be placed; may not be null
594      * @param largeValues the interface to use for writing large values; may not be null
595      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
596      * @throws ClassNotFoundException if the class for the value's object could not be found
597      * @see #deserializePropertyValues(ObjectInputStream, Name, boolean, LargeValues, LargeValues, ReferenceValues)
598      * @see #serializeProperties(ObjectOutputStream, int, Iterable, LargeValues, ReferenceValues)
599      */
600     public void deserializeAllProperties( ObjectInputStream stream,
601                                           Collection<Property> properties,
602                                           LargeValues largeValues ) throws IOException, ClassNotFoundException {
603         assert stream != null;
604         assert properties != null;
605         // Read the number of properties ...
606         int count = stream.readInt();
607         for (int i = 0; i != count; ++i) {
608             Property property = deserializeProperty(stream, largeValues);
609             assert property != null;
610             properties.add(property);
611         }
612     }
613 
614     /**
615      * Deserialize the serialized properties on the supplied object stream.
616      * 
617      * @param stream the stream that contains the serialized properties; may not be null
618      * @param properties the collection into which each deserialized property is to be placed; may not be null
619      * @param names the names of the properties that should be deserialized; should not be null or empty
620      * @param largeValues the interface to use for writing large values; may not be null
621      * @param skippedLargeValues the interface to use for recording the large values that were skipped; may not be null
622      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
623      * @throws ClassNotFoundException if the class for the value's object could not be found
624      * @see #deserializePropertyValues(ObjectInputStream, Name, boolean, LargeValues, LargeValues, ReferenceValues)
625      * @see #serializeProperties(ObjectOutputStream, int, Iterable, LargeValues, ReferenceValues)
626      */
627     public void deserializeSomeProperties( ObjectInputStream stream,
628                                            Collection<Property> properties,
629                                            LargeValues largeValues,
630                                            LargeValues skippedLargeValues,
631                                            Name... names ) throws IOException, ClassNotFoundException {
632         assert stream != null;
633         assert properties != null;
634         assert names != null;
635         assert names.length > 0;
636         Name nameToRead = null;
637         Set<Name> namesToRead = null;
638         if (names.length == 1) {
639             nameToRead = names[0];
640         } else {
641             namesToRead = new HashSet<Name>();
642             for (Name name : names) {
643                 if (name != null) namesToRead.add(name);
644             }
645         }
646 
647         // Read the number of properties ...
648         boolean read = false;
649         int count = stream.readInt();
650 
651         // Now, read the properties (or skip the ones that we're not supposed to read) ...
652         for (int i = 0; i != count; ++i) {
653             // Read the name ...
654             String nameStr = (String)stream.readObject();
655             Name name = valueFactories.getNameFactory().create(nameStr);
656             assert name != null;
657             read = name.equals(nameToRead) || (namesToRead != null && namesToRead.contains(namesToRead));
658             if (read) {
659                 // Now read the property values ...
660                 Object[] values = deserializePropertyValues(stream, name, false, largeValues, skippedLargeValues, null);
661                 // Add the property to the collection ...
662                 Property property = propertyFactory.create(name, values);
663                 assert property != null;
664                 properties.add(property);
665             } else {
666                 // Skip the property ...
667                 deserializePropertyValues(stream, name, true, largeValues, skippedLargeValues, null);
668             }
669         }
670     }
671 
672     /**
673      * Deserialize the serialized property on the supplied object stream.
674      * 
675      * @param stream the stream that contains the serialized properties; may not be null
676      * @param largeValues the interface to use for writing large values; may not be null
677      * @return the deserialized property; never null
678      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
679      * @throws ClassNotFoundException if the class for the value's object could not be found
680      * @see #deserializeAllProperties(ObjectInputStream, Collection, LargeValues)
681      * @see #serializeProperty(ObjectOutputStream, Property, LargeValues, ReferenceValues)
682      */
683     public Property deserializeProperty( ObjectInputStream stream,
684                                          LargeValues largeValues ) throws IOException, ClassNotFoundException {
685         // Read the name ...
686         String nameStr = (String)stream.readObject();
687         Name name = valueFactories.getNameFactory().create(nameStr);
688         assert name != null;
689         // Now read the property values ...
690         Object[] values = deserializePropertyValues(stream, name, false, largeValues, largeValues, null);
691         // Add the property to the collection ...
692         return propertyFactory.create(name, values);
693     }
694 
695     /**
696      * Deserialize the serialized property on the supplied object stream.
697      * 
698      * @param stream the stream that contains the serialized properties; may not be null
699      * @param propertyName the name of the property being deserialized
700      * @param skip true if the values don't need to be read, or false if they are to be read
701      * @param largeValues the interface to use for writing large values; may not be null
702      * @param skippedLargeValues the interface to use for recording the large values that were skipped; may not be null
703      * @param references the interface to use for recording which {@link Reference} values were found (and/or removed) during
704      *        deserialization; may not be null
705      * @return the deserialized property values, or an empty list if there are no values
706      * @throws IOException if there is an error writing to the <code>stream</code> or <code>largeValues</code>
707      * @throws ClassNotFoundException if the class for the value's object could not be found
708      * @see #deserializeAllProperties(ObjectInputStream, Collection, LargeValues)
709      * @see #serializeProperty(ObjectOutputStream, Property, LargeValues, ReferenceValues)
710      */
711     public Object[] deserializePropertyValues( ObjectInputStream stream,
712                                                Name propertyName,
713                                                boolean skip,
714                                                LargeValues largeValues,
715                                                LargeValues skippedLargeValues,
716                                                ReferenceValues references ) throws IOException, ClassNotFoundException {
717         assert stream != null;
718         assert propertyName != null;
719         assert largeValues != null;
720         assert skippedLargeValues != null;
721         // Read the number of values ...
722         int size = stream.readInt();
723         Object[] values = skip ? null : new Object[size];
724         for (int i = 0; i != size; ++i) {
725             Object value = null;
726             // Read the type of value ...
727             char type = stream.readChar();
728             switch (type) {
729                 case 'S':
730                     String stringValue = (String)stream.readObject();
731                     if (!skip) value = valueFactories.getStringFactory().create(stringValue);
732                     break;
733                 case 'b':
734                     boolean booleanValue = stream.readBoolean();
735                     if (!skip) value = valueFactories.getBooleanFactory().create(booleanValue);
736                     break;
737                 case 'i':
738                     int intValue = stream.readInt();
739                     if (!skip) value = valueFactories.getLongFactory().create(intValue);
740                     break;
741                 case 'l':
742                     long longValue = stream.readLong();
743                     if (!skip) value = valueFactories.getLongFactory().create(longValue);
744                     break;
745                 case 's':
746                     short shortValue = stream.readShort();
747                     if (!skip) value = valueFactories.getLongFactory().create(shortValue);
748                     break;
749                 case 'f':
750                     float floatValue = stream.readFloat();
751                     if (!skip) value = valueFactories.getDoubleFactory().create(floatValue);
752                     break;
753                 case 'd':
754                     double doubleValue = stream.readDouble();
755                     if (!skip) value = valueFactories.getDoubleFactory().create(doubleValue);
756                     break;
757                 case 'c':
758                     // char
759                     String charValue = "" + stream.readChar();
760                     if (!skip) value = valueFactories.getStringFactory().create(charValue);
761                     break;
762                 case 'U':
763                     // UUID
764                     long msb = stream.readLong();
765                     long lsb = stream.readLong();
766                     if (!skip) {
767                         UUID uuid = new UUID(msb, lsb);
768                         value = valueFactories.getUuidFactory().create(uuid);
769                     }
770                     break;
771                 case 'I':
772                     // URI
773                     String uriStr = (String)stream.readObject();
774                     if (!skip) value = valueFactories.getUriFactory().create(uriStr);
775                     break;
776                 case 'N':
777                     // Name
778                     String nameValueStr = (String)stream.readObject();
779                     if (!skip) value = valueFactories.getNameFactory().create(nameValueStr);
780                     break;
781                 case 'P':
782                     // Path
783                     String pathStr = (String)stream.readObject();
784                     if (!skip) value = valueFactories.getPathFactory().create(pathStr);
785                     break;
786                 case 'T':
787                     // DateTime
788                     String dateTimeStr = (String)stream.readObject();
789                     if (!skip) value = valueFactories.getDateFactory().create(dateTimeStr);
790                     break;
791                 case 'D':
792                     // BigDecimal
793                     Object bigDecimal = stream.readObject();
794                     if (!skip) value = valueFactories.getDecimalFactory().create(bigDecimal);
795                     break;
796                 case 'R':
797                     // Reference
798                     String refValue = (String)stream.readObject();
799                     Reference ref = valueFactories.getReferenceFactory().create(refValue);
800                     if (skip) {
801                         if (references != null) references.remove(ref);
802                     } else {
803                         value = ref;
804                         if (references != null) references.read(ref);
805                     }
806                     break;
807                 case 'W':
808                     // Weak reference
809                     refValue = (String)stream.readObject();
810                     ref = valueFactories.getWeakReferenceFactory().create(refValue);
811                     if (skip) {
812                         if (references != null) references.remove(ref);
813                     } else {
814                         value = ref;
815                         if (references != null) references.read(ref);
816                     }
817                     break;
818                 case 'B':
819                     // Binary
820                     // Read the length of the content ...
821                     long binaryLength = stream.readLong();
822                     byte[] content = new byte[(int)binaryLength];
823                     stream.readFully(content, 0, content.length);
824                     if (!skip) {
825                         value = valueFactories.getBinaryFactory().create(content);
826                     }
827                     break;
828                 case 'L':
829                     // Large object ...
830                     // Read the hash ...
831                     int hashLength = stream.readInt();
832                     byte[] hash = new byte[hashLength];
833                     stream.readFully(hash, 0, hashLength);
834                     // Read the length of the content ...
835                     long length = stream.readLong();
836                     if (skip) {
837                         skippedLargeValues.read(valueFactories, hash, length);
838                     } else {
839                         BinaryFactory factory = valueFactories.getBinaryFactory();
840                         // Look for an already-loaded Binary value with the same hash ...
841                         value = factory.find(hash);
842                         if (value == null) {
843                             // Didn't find an existing large value, so we have to read the large value ...
844                             value = largeValues.read(valueFactories, hash, length);
845                         }
846                     }
847                     break;
848                 default:
849                     // All other objects ...
850                     Object object = stream.readObject();
851                     if (!skip) value = valueFactories.getObjectFactory().create(object);
852                     break;
853             }
854             if (value != null) values[i] = value;
855         }
856         return values;
857     }
858 
859     public byte[] computeHash( String value ) {
860         try {
861             return SecureHash.getHash(SecureHash.Algorithm.SHA_1, value.getBytes());
862         } catch (NoSuchAlgorithmException e) {
863             throw new SystemFailureException(e);
864         }
865     }
866 }