001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *  http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.directory.shared.ldap.model.entry;
020
021
022import java.io.IOException;
023import java.io.ObjectInput;
024import java.io.ObjectOutput;
025import java.util.Arrays;
026import java.util.Comparator;
027
028import org.apache.directory.shared.i18n.I18n;
029import org.apache.directory.shared.ldap.model.exception.LdapException;
030import org.apache.directory.shared.ldap.model.exception.LdapInvalidAttributeValueException;
031import org.apache.directory.shared.ldap.model.schema.AttributeType;
032import org.apache.directory.shared.ldap.model.schema.LdapComparator;
033import org.apache.directory.shared.ldap.model.schema.comparators.ByteArrayComparator;
034import org.apache.directory.shared.util.Strings;
035
036
037/**
038 * A server side schema aware wrapper around a binary attribute value.
039 * This value wrapper uses schema information to syntax check values,
040 * and to compare them for equality and ordering.  It caches results
041 * and invalidates them when the wrapped value changes.
042 *
043 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
044 */
045public class BinaryValue extends AbstractValue<byte[]>
046{
047    /** Used for serialization */
048    public static final long serialVersionUID = 2L;
049
050    /**
051     * Creates a BinaryValue without an initial wrapped value.
052     *
053     * @param attributeType the schema type associated with this BinaryValue
054     */
055    /* No protection */ BinaryValue( AttributeType attributeType )
056    {
057        if ( attributeType != null )
058        {
059            // We must have a Syntax
060            if ( attributeType.getSyntax() == null )
061            {
062                throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) );
063            }
064    
065            if ( attributeType.getSyntax().isHumanReadable() )
066            {
067                LOG.warn( "Treating a value of a human readible attribute {} as binary: ", attributeType.getName() );
068            }
069    
070            this.attributeType = attributeType;
071        }
072    }
073
074
075    /**
076     * Creates a BinaryValue with an initial wrapped binary value.
077     *
078     * @param value the binary value to wrap which may be null, or a zero length byte array
079     */
080    public BinaryValue( byte[] value )
081    {
082        if ( value != null )
083        {
084            this.wrappedValue = new byte[value.length];
085            this.normalizedValue = new byte[value.length];
086            System.arraycopy( value, 0, this.wrappedValue, 0, value.length );
087            System.arraycopy( value, 0, this.normalizedValue, 0, value.length );
088        }
089        else
090        {
091            this.wrappedValue = null;
092            this.normalizedValue = null;
093        }
094    }
095
096
097    /**
098     * Creates a BinaryValue with an initial wrapped binary value.
099     *
100     * @param attributeType the schema type associated with this BinaryValue
101     * @param value the binary value to wrap which may be null, or a zero length byte array
102     * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly 
103     * to the schema
104     */
105    public BinaryValue( AttributeType attributeType, byte[] value ) throws LdapInvalidAttributeValueException
106    {
107        this( value );
108        apply( attributeType );
109    }
110
111
112    /**
113     * Gets a direct reference to the normalized representation for the
114     * wrapped value of this ServerValue wrapper. Implementations will most
115     * likely leverage the attributeType this value is associated with to
116     * determine how to properly normalize the wrapped value.
117     *
118     * @return the normalized version of the wrapped value
119     */
120    public byte[] getNormValue()
121    {
122        if ( isNull() )
123        {
124            return null;
125        }
126
127        byte[] copy = new byte[normalizedValue.length];
128        System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length );
129        return copy;
130    }
131
132
133    /**
134     *
135     * @see ServerValue#compareTo(Value)
136     */
137    public int compareTo( Value<byte[]> value )
138    {
139        if ( isNull() )
140        {
141            if ( ( value == null ) || value.isNull() )
142            {
143                return 0;
144            }
145            else
146            {
147                return -1;
148            }
149        }
150        else
151        {
152            if ( ( value == null ) || value.isNull() )
153            {
154                return 1;
155            }
156        }
157
158        BinaryValue binaryValue = ( BinaryValue ) value;
159
160        if ( attributeType != null )
161        {
162            try
163            {
164                LdapComparator<byte[]> comparator = getLdapComparator();
165
166                if ( comparator != null )
167                {
168                    return comparator
169                        .compare( getNormReference(), binaryValue.getNormReference() );
170                }
171                else
172                {
173                    return new ByteArrayComparator( null ).compare( getNormReference(), binaryValue
174                        .getNormReference() );
175                }
176            }
177            catch ( LdapException e )
178            {
179                String msg = I18n.err( I18n.ERR_04443, Arrays.toString( getReference() ), value );
180                LOG.error( msg, e );
181                throw new IllegalStateException( msg, e );
182            }
183        }
184        else
185        {
186            return new ByteArrayComparator( null ).compare( getNormValue(), binaryValue.getNormValue() );
187        }
188    }
189
190
191    // -----------------------------------------------------------------------
192    // Object Methods
193    // -----------------------------------------------------------------------
194    /**
195     * @see Object#hashCode()
196     * @return the instance's hashcode 
197     */
198    public int hashCode()
199    {
200        if ( h == 0 )
201        {
202            // return zero if the value is null so only one null value can be
203            // stored in an attribute - the string version does the same
204            if ( isNull() )
205            {
206                return 0;
207            }
208
209            byte[] normalizedValue = getNormReference();
210            h = Arrays.hashCode( normalizedValue );
211        }
212
213        return h;
214    }
215
216
217    /**
218     * Checks to see if this BinaryValue equals the supplied object.
219     *
220     * This equals implementation overrides the BinaryValue implementation which
221     * is not schema aware.
222     */
223    public boolean equals( Object obj )
224    {
225        if ( this == obj )
226        {
227            return true;
228        }
229
230        if ( !( obj instanceof BinaryValue ) )
231        {
232            return false;
233        }
234
235        BinaryValue other = ( BinaryValue ) obj;
236
237        if ( isNull() )
238        {
239            return other.isNull();
240        }
241
242        // If we have an attributeType, it must be equal
243        // We should also use the comparator if we have an AT
244        if ( attributeType != null )
245        {
246            if ( other.attributeType != null )
247            {
248                if ( !attributeType.equals( other.attributeType ) )
249                {
250                    return false;
251                }
252            }
253            else
254            {
255                other.attributeType = attributeType;
256            }
257        }
258        else if ( other.attributeType != null )
259        {
260            attributeType = other.attributeType;
261        }
262
263        // Shortcut : if the values are equals, no need to compare
264        // the normalized values
265        if ( Arrays.equals( wrappedValue, other.wrappedValue ) )
266        {
267            return true;
268        }
269
270        if ( attributeType != null )
271        {
272            // We have an AttributeType, we eed to use the comparator
273            try
274            {
275                Comparator<byte[]> comparator = ( Comparator<byte[]> ) getLdapComparator();
276
277                // Compare normalized values
278                if ( comparator == null )
279                {
280                    return Arrays.equals( getNormReference(), other.getNormReference() );
281                }
282                else
283                {
284                    return comparator.compare( getNormReference(), other.getNormReference() ) == 0;
285                }
286            }
287            catch ( LdapException ne )
288            {
289                return false;
290            }
291
292        }
293        else
294        {
295            // now unlike regular values we have to compare the normalized values
296            return Arrays.equals( getNormReference(), other.getNormReference() );
297        }
298    }
299
300
301    // -----------------------------------------------------------------------
302    // Cloneable methods
303    // -----------------------------------------------------------------------
304    /**
305     * {@inheritDoc}
306     */
307    public BinaryValue clone()
308    {
309        BinaryValue clone = ( BinaryValue ) super.clone();
310
311        // We have to copy the byte[], they are just referenced by suoer.clone()
312        if ( normalizedValue != null )
313        {
314            clone.normalizedValue = new byte[normalizedValue.length];
315            System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length );
316        }
317
318        if ( wrappedValue != null )
319        {
320            clone.wrappedValue = new byte[wrappedValue.length];
321            System.arraycopy( wrappedValue, 0, clone.wrappedValue, 0, wrappedValue.length );
322        }
323
324        return clone;
325    }
326
327
328    /**
329     * {@inheritDoc}
330     */
331    public byte[] getValue()
332    {
333        if ( wrappedValue == null )
334        {
335            return null;
336        }
337
338        final byte[] copy = new byte[wrappedValue.length];
339        System.arraycopy( wrappedValue, 0, copy, 0, wrappedValue.length );
340
341        return copy;
342    }
343
344
345    /**
346     * Tells if the current value is Human Readable
347     * 
348     * @return <code>true</code> if the value is HR, <code>false</code> otherwise
349     */
350    public boolean isHumanReadable()
351    {
352        return false;
353    }
354
355
356    /**
357     * @return The length of the interned value
358     */
359    public int length()
360    {
361        return wrappedValue != null ? wrappedValue.length : 0;
362    }
363
364
365    /**
366     * Get the wrapped value as a byte[]. This method returns a copy of 
367     * the wrapped byte[].
368     * 
369     * @return the wrapped value as a byte[]
370     */
371    public byte[] getBytes()
372    {
373        return getValue();
374    }
375
376
377    /**
378     * Get the wrapped value as a String.
379     *
380     * @return the wrapped value as a String
381     */
382    public String getString()
383    {
384        return Strings.utf8ToString( wrappedValue );
385    }
386
387
388    /**
389     * Deserialize a BinaryValue. It will return a new BinaryValue instance.
390     * 
391     * @param attributeType The AttributeType associated with the Value. Can be null
392     * @param in The input stream
393     * @return A new StringValue instance
394     * @throws IOException If the stream can't be read
395     * @throws ClassNotFoundException If we can't instanciate a BinaryValue
396     */
397    public static BinaryValue deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
398    {
399        BinaryValue value = new BinaryValue( (AttributeType)null );
400        value.readExternal( in );
401
402        return value;
403    }
404
405
406    /**
407     * Deserialize a schema aware BinaryValue. It will return a new BinaryValue instance.
408     * 
409     * @param attributeType The AttributeType associated with the Value. Can be null
410     * @param in The input stream
411     * @return A new StringValue instance
412     * @throws IOException If the stream can't be read
413     * @throws ClassNotFoundException If we can't instanciate a BinaryValue
414     */
415    public static BinaryValue deserialize( AttributeType attributeType, ObjectInput in ) throws IOException, ClassNotFoundException
416    {
417        BinaryValue value = new BinaryValue( attributeType );
418        value.readExternal( in );
419
420        return value;
421    }
422    
423    
424    /**
425     * {@inheritDoc}
426     */
427    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
428    {
429        // Read the BINARY flag
430        boolean isHR = in.readBoolean();
431        
432        if ( isHR )
433        {
434            throw new IOException( "The serialized value is not a Binary value" );
435        }
436        // Read the wrapped value, if it's not null
437        int wrappedLength = in.readInt();
438
439        if ( wrappedLength >= 0 )
440        {
441            wrappedValue = new byte[wrappedLength];
442
443            if ( wrappedLength > 0 &&  in.read( wrappedValue ) == -1 )
444            {
445                throw new IOException( I18n.err( I18n.ERR_04480_END_OF_STREAM ) );
446            }
447        }
448
449        // Read the isNormalized flag
450        boolean normalized = in.readBoolean();
451
452        if ( normalized )
453        {
454            int normalizedLength = in.readInt();
455
456            if ( normalizedLength >= 0 )
457            {
458                normalizedValue = new byte[normalizedLength];
459
460                if ( normalizedLength > 0 )
461                {
462                    in.read( normalizedValue );
463                }
464            }
465        }
466        else
467        {
468            // Copy the wrappedValue into the normalizedValue
469            if ( wrappedLength >= 0 )
470            {
471                normalizedValue = new byte[wrappedLength];
472                
473                System.arraycopy( wrappedValue, 0, normalizedValue, 0, wrappedLength );
474            }
475        }
476
477        // The hashCoe
478        h = in.readInt();
479    }
480
481
482    /**
483     * {@inheritDoc}
484     */
485    public void writeExternal( ObjectOutput out ) throws IOException
486    {
487        // Write the BINARY flag
488        out.writeBoolean( BINARY );
489        
490        // Write the wrapped value, if it's not null
491        if ( wrappedValue != null )
492        {
493            out.writeInt( wrappedValue.length );
494
495            if ( wrappedValue.length > 0 )
496            {
497                out.write( wrappedValue, 0, wrappedValue.length );
498            }
499        }
500        else
501        {
502            out.writeInt( -1 );
503        }
504
505        // Write the isNormalized flag
506        if ( attributeType != null )
507        {
508            out.writeBoolean( true );
509
510            // Write the normalized value, if not null
511            if ( normalizedValue != null )
512            {
513                out.writeInt( normalizedValue.length );
514
515                if ( normalizedValue.length > 0 )
516                {
517                    out.write( normalizedValue, 0, normalizedValue.length );
518                }
519            }
520            else
521            {
522                out.writeInt( -1 );
523            }
524        }
525        else
526        {
527            out.writeBoolean( false );
528        }
529        
530        // The hashCode
531        out.writeInt( h );
532        
533        out.flush();
534    }
535
536    
537    /**
538     * Dumps binary in hex with label.
539     *
540     * @see Object#toString()
541     */
542    public String toString()
543    {
544        if ( wrappedValue == null )
545        {
546            return "null";
547        }
548        else if ( wrappedValue.length > 16 )
549        {
550            // Just dump the first 16 bytes...
551            byte[] copy = new byte[16];
552
553            System.arraycopy( wrappedValue, 0, copy, 0, 16 );
554
555            return "'" + Strings.dumpBytes(copy) + "...'";
556        }
557        else
558        {
559            return "'" + Strings.dumpBytes(wrappedValue) + "'";
560        }
561    }
562}