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.api.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.api.i18n.I18n;
029import org.apache.directory.api.ldap.model.exception.LdapException;
030import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
031import org.apache.directory.api.ldap.model.schema.AttributeType;
032import org.apache.directory.api.ldap.model.schema.LdapComparator;
033import org.apache.directory.api.ldap.model.schema.MatchingRule;
034import org.apache.directory.api.ldap.model.schema.Normalizer;
035import org.apache.directory.api.ldap.model.schema.comparators.ByteArrayComparator;
036import org.apache.directory.api.util.Strings;
037
038
039/**
040 * A server side schema aware wrapper around a binary attribute value.
041 * This value wrapper uses schema information to syntax check values,
042 * and to compare them for equality and ordering.  It caches results
043 * and invalidates them when the wrapped value changes.
044 *
045 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
046 */
047public class BinaryValue extends AbstractValue<byte[]>
048{
049    /** Used for serialization */
050    public static final long serialVersionUID = 2L;
051
052
053    /**
054     * Creates a BinaryValue without an initial wrapped value.
055     *
056     * @param attributeType the schema type associated with this BinaryValue
057     */
058    /* No protection */BinaryValue( AttributeType attributeType )
059    {
060        if ( attributeType != null )
061        {
062            // We must have a Syntax
063            if ( attributeType.getSyntax() == null )
064            {
065                throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) );
066            }
067
068            if ( attributeType.getSyntax().isHumanReadable() )
069            {
070                LOG.warn( "Treating a value of a human readible attribute {} as binary: ", attributeType.getName() );
071            }
072
073            this.attributeType = attributeType;
074        }
075    }
076
077
078    /**
079     * Creates a BinaryValue with an initial wrapped binary value.
080     *
081     * @param value the binary value to wrap which may be null, or a zero length byte array
082     */
083    public BinaryValue( byte[] value )
084    {
085        if ( value != null )
086        {
087            this.wrappedValue = new byte[value.length];
088            this.normalizedValue = new byte[value.length];
089            System.arraycopy( value, 0, this.wrappedValue, 0, value.length );
090            System.arraycopy( value, 0, this.normalizedValue, 0, value.length );
091        }
092        else
093        {
094            this.wrappedValue = null;
095            this.normalizedValue = null;
096        }
097    }
098
099
100    /**
101     * Creates a BinaryValue with an initial wrapped binary value.
102     *
103     * @param attributeType the schema type associated with this BinaryValue
104     * @param value the binary value to wrap which may be null, or a zero length byte array
105     * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly 
106     * to the schema
107     */
108    public BinaryValue( AttributeType attributeType, byte[] value ) throws LdapInvalidAttributeValueException
109    {
110        this( value );
111        apply( attributeType );
112    }
113
114
115    /**
116     * Gets a direct reference to the normalized representation for the
117     * wrapped value of this ServerValue wrapper. Implementations will most
118     * likely leverage the attributeType this value is associated with to
119     * determine how to properly normalize the wrapped value.
120     *
121     * @return the normalized version of the wrapped value
122     */
123    public byte[] getNormValue()
124    {
125        if ( isNull() )
126        {
127            return null;
128        }
129
130        byte[] copy = new byte[normalizedValue.length];
131        System.arraycopy( normalizedValue, 0, copy, 0, normalizedValue.length );
132        return copy;
133    }
134
135
136    /**
137     *
138     * @see ServerValue#compareTo(Value)
139     */
140    public int compareTo( Value<byte[]> value )
141    {
142        if ( isNull() )
143        {
144            if ( ( value == null ) || value.isNull() )
145            {
146                return 0;
147            }
148            else
149            {
150                return -1;
151            }
152        }
153        else
154        {
155            if ( ( value == null ) || value.isNull() )
156            {
157                return 1;
158            }
159        }
160
161        BinaryValue binaryValue = ( BinaryValue ) value;
162
163        if ( attributeType != null )
164        {
165            try
166            {
167                LdapComparator<byte[]> comparator = getLdapComparator();
168
169                if ( comparator != null )
170                {
171                    return comparator
172                        .compare( getNormReference(), binaryValue.getNormReference() );
173                }
174                else
175                {
176                    return new ByteArrayComparator( null ).compare( getNormReference(), binaryValue
177                        .getNormReference() );
178                }
179            }
180            catch ( LdapException e )
181            {
182                String msg = I18n.err( I18n.ERR_04443, Arrays.toString( getReference() ), value );
183                LOG.error( msg, e );
184                throw new IllegalStateException( msg, e );
185            }
186        }
187        else
188        {
189            return new ByteArrayComparator( null ).compare( getNormValue(), binaryValue.getNormValue() );
190        }
191    }
192
193
194    // -----------------------------------------------------------------------
195    // Object Methods
196    // -----------------------------------------------------------------------
197    /**
198     * @see Object#hashCode()
199     * @return the instance's hashcode 
200     */
201    public int hashCode()
202    {
203        if ( h == 0 )
204        {
205            // return zero if the value is null so only one null value can be
206            // stored in an attribute - the string version does the same
207            if ( isNull() )
208            {
209                return 0;
210            }
211
212            byte[] normalizedValue = getNormReference();
213            h = Arrays.hashCode( normalizedValue );
214        }
215
216        return h;
217    }
218
219
220    /**
221     * Checks to see if this BinaryValue equals the supplied object.
222     *
223     * This equals implementation overrides the BinaryValue implementation which
224     * is not schema aware.
225     */
226    public boolean equals( Object obj )
227    {
228        if ( this == obj )
229        {
230            return true;
231        }
232
233        if ( !( obj instanceof BinaryValue ) )
234        {
235            return false;
236        }
237
238        BinaryValue other = ( BinaryValue ) obj;
239
240        // First check if we have an attrbuteType.
241        if ( attributeType != null )
242        {
243            // yes : check for the other value
244            if ( other.attributeType != null )
245            {
246                if ( attributeType.getOid().equals( other.getAttributeType().getOid() ) )
247                {
248                    // Both AttributeType have the same OID, we can assume they are 
249                    // equals. We don't check any further, because the unicity of OID
250                    // makes it unlikely that the two AT are different.
251                    // The values may be both null
252                    if ( isNull() )
253                    {
254                        return other.isNull();
255                    }
256
257                    // Shortcut : if we have an AT for both the values, check the 
258                    // already normalized values
259                    if ( Arrays.equals( wrappedValue, other.wrappedValue ) )
260                    {
261                        return true;
262                    }
263
264                    // We have an AttributeType, we use the associated comparator
265                    try
266                    {
267                        Comparator<byte[]> comparator = getLdapComparator();
268
269                        // Compare normalized values
270                        if ( comparator == null )
271                        {
272                            return Arrays.equals( getNormReference(), other.getNormReference() );
273                        }
274                        else
275                        {
276                            return comparator.compare( getNormReference(), other.getNormReference() ) == 0;
277                        }
278                    }
279                    catch ( LdapException ne )
280                    {
281                        return false;
282                    }
283                }
284                else
285                {
286                    // We can't compare two values when the two ATs are different
287                    return false;
288                }
289            }
290            else
291            {
292                // We only have one AT : we will assume that both values are for the 
293                // same AT.
294                // The values may be both null
295                if ( isNull() )
296                {
297                    return other.isNull();
298                }
299
300                // We have an AttributeType on the base value, we need to use its comparator
301                try
302                {
303                    Comparator<byte[]> comparator = getLdapComparator();
304
305                    // Compare normalized values. We have to normalized the other value,
306                    // as it has no AT
307                    MatchingRule equality = getAttributeType().getEquality();
308
309                    if ( equality == null )
310                    {
311                        // No matching rule : compare the raw values
312                        return Arrays.equals( getNormReference(), other.getNormReference() );
313                    }
314
315                    Normalizer normalizer = equality.getNormalizer();
316
317                    BinaryValue otherValue = ( BinaryValue ) normalizer.normalize( other );
318
319                    if ( comparator == null )
320                    {
321                        return Arrays.equals( getNormReference(), otherValue.getNormReference() );
322                    }
323                    else
324                    {
325                        return comparator.compare( getNormReference(), otherValue.getNormReference() ) == 0;
326                    }
327                }
328                catch ( LdapException ne )
329                {
330                    return false;
331                }
332            }
333        }
334        else
335        {
336            // No : check for the other value
337            if ( other.attributeType != null )
338            {
339                // We only have one AT : we will assume that both values are for the 
340                // same AT.
341                // The values may be both null
342                if ( isNull() )
343                {
344                    return other.isNull();
345                }
346
347                try
348                {
349                    Comparator<byte[]> comparator = other.getLdapComparator();
350
351                    // Compare normalized values. We have to normalized the other value,
352                    // as it has no AT
353                    MatchingRule equality = other.getAttributeType().getEquality();
354
355                    if ( equality == null )
356                    {
357                        // No matching rule : compare the raw values
358                        return Arrays.equals( getNormReference(), other.getNormReference() );
359                    }
360
361                    Normalizer normalizer = equality.getNormalizer();
362
363                    BinaryValue thisValue = ( BinaryValue ) normalizer.normalize( this );
364
365                    if ( comparator == null )
366                    {
367                        return Arrays.equals( thisValue.getNormReference(), other.getNormReference() );
368                    }
369                    else
370                    {
371                        return comparator.compare( thisValue.getNormReference(), other.getNormReference() ) == 0;
372                    }
373                }
374                catch ( LdapException ne )
375                {
376                    return false;
377                }
378            }
379            else
380            {
381                // The values may be both null
382                if ( isNull() )
383                {
384                    return other.isNull();
385                }
386
387                // Now check the normalized values
388                return Arrays.equals( getNormReference(), other.getNormReference() );
389            }
390        }
391    }
392
393
394    // -----------------------------------------------------------------------
395    // Cloneable methods
396    // -----------------------------------------------------------------------
397    /**
398     * {@inheritDoc}
399     */
400    public BinaryValue clone()
401    {
402        BinaryValue clone = ( BinaryValue ) super.clone();
403
404        // We have to copy the byte[], they are just referenced by suoer.clone()
405        if ( normalizedValue != null )
406        {
407            clone.normalizedValue = new byte[normalizedValue.length];
408            System.arraycopy( normalizedValue, 0, clone.normalizedValue, 0, normalizedValue.length );
409        }
410
411        if ( wrappedValue != null )
412        {
413            clone.wrappedValue = new byte[wrappedValue.length];
414            System.arraycopy( wrappedValue, 0, clone.wrappedValue, 0, wrappedValue.length );
415        }
416
417        return clone;
418    }
419
420
421    /**
422     * {@inheritDoc}
423     */
424    public byte[] getValue()
425    {
426        if ( wrappedValue == null )
427        {
428            return null;
429        }
430
431        final byte[] copy = new byte[wrappedValue.length];
432        System.arraycopy( wrappedValue, 0, copy, 0, wrappedValue.length );
433
434        return copy;
435    }
436
437
438    /**
439     * Tells if the current value is Human Readable
440     * 
441     * @return <code>true</code> if the value is HR, <code>false</code> otherwise
442     */
443    public boolean isHumanReadable()
444    {
445        return false;
446    }
447
448
449    /**
450     * @return The length of the interned value
451     */
452    public int length()
453    {
454        return wrappedValue != null ? wrappedValue.length : 0;
455    }
456
457
458    /**
459     * Get the wrapped value as a byte[]. This method returns a copy of 
460     * the wrapped byte[].
461     * 
462     * @return the wrapped value as a byte[]
463     */
464    public byte[] getBytes()
465    {
466        return getValue();
467    }
468
469
470    /**
471     * Get the wrapped value as a String.
472     *
473     * @return the wrapped value as a String
474     */
475    public String getString()
476    {
477        return Strings.utf8ToString( wrappedValue );
478    }
479
480
481    /**
482     * Deserialize a BinaryValue. It will return a new BinaryValue instance.
483     * 
484     * @param attributeType The AttributeType associated with the Value. Can be null
485     * @param in The input stream
486     * @return A new StringValue instance
487     * @throws IOException If the stream can't be read
488     * @throws ClassNotFoundException If we can't instanciate a BinaryValue
489     */
490    public static BinaryValue deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
491    {
492        BinaryValue value = new BinaryValue( ( AttributeType ) null );
493        value.readExternal( in );
494
495        return value;
496    }
497
498
499    /**
500     * Deserialize a schema aware BinaryValue. It will return a new BinaryValue instance.
501     * 
502     * @param attributeType The AttributeType associated with the Value. Can be null
503     * @param in The input stream
504     * @return A new StringValue instance
505     * @throws IOException If the stream can't be read
506     * @throws ClassNotFoundException If we can't instanciate a BinaryValue
507     */
508    public static BinaryValue deserialize( AttributeType attributeType, ObjectInput in ) throws IOException,
509        ClassNotFoundException
510    {
511        BinaryValue value = new BinaryValue( attributeType );
512        value.readExternal( in );
513
514        return value;
515    }
516
517
518    /**
519     * {@inheritDoc}
520     */
521    public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
522    {
523        // Read the BINARY flag
524        boolean isHR = in.readBoolean();
525
526        if ( isHR )
527        {
528            throw new IOException( "The serialized value is not a Binary value" );
529        }
530        // Read the wrapped value, if it's not null
531        int wrappedLength = in.readInt();
532
533        if ( wrappedLength >= 0 )
534        {
535            wrappedValue = new byte[wrappedLength];
536
537            in.readFully( wrappedValue );
538        }
539
540        // Read the isNormalized flag
541        boolean normalized = in.readBoolean();
542
543        if ( normalized )
544        {
545            int normalizedLength = in.readInt();
546
547            if ( normalizedLength >= 0 )
548            {
549                normalizedValue = new byte[normalizedLength];
550
551                in.readFully( normalizedValue );
552            }
553        }
554        else
555        {
556            if ( attributeType != null )
557            {
558                try
559                {
560                    normalizedValue = attributeType.getEquality().getNormalizer().normalize( this ).getBytes();
561                    MatchingRule equality = attributeType.getEquality();
562
563                    if ( equality == null )
564                    {
565                        if ( wrappedLength >= 0 )
566                        {
567                            normalizedValue = new byte[wrappedLength];
568
569                            System.arraycopy( wrappedValue, 0, normalizedValue, 0, wrappedLength );
570                        }
571                    }
572                    else
573                    {
574                        Normalizer normalizer = equality.getNormalizer();
575
576                        if ( normalizer != null )
577                        {
578                            normalizedValue = normalizer.normalize( this ).getBytes();
579                        }
580                        else
581                        {
582                            if ( wrappedLength >= 0 )
583                            {
584                                normalizedValue = new byte[wrappedLength];
585
586                                System.arraycopy( wrappedValue, 0, normalizedValue, 0, wrappedLength );
587                            }
588                        }
589                    }
590                }
591                catch ( LdapException le )
592                {
593                    // Copy the wrappedValue into the normalizedValue
594                    if ( wrappedLength >= 0 )
595                    {
596                        normalizedValue = new byte[wrappedLength];
597
598                        System.arraycopy( wrappedValue, 0, normalizedValue, 0, wrappedLength );
599                    }
600                }
601            }
602            else
603            {
604                // Copy the wrappedValue into the normalizedValue
605                if ( wrappedLength >= 0 )
606                {
607                    normalizedValue = new byte[wrappedLength];
608
609                    System.arraycopy( wrappedValue, 0, normalizedValue, 0, wrappedLength );
610                }
611            }
612        }
613
614        // The hashCoe
615        h = in.readInt();
616    }
617
618
619    /**
620     * {@inheritDoc}
621     */
622    public void writeExternal( ObjectOutput out ) throws IOException
623    {
624        // Write the BINARY flag
625        out.writeBoolean( BINARY );
626
627        // Write the wrapped value, if it's not null
628        if ( wrappedValue != null )
629        {
630            out.writeInt( wrappedValue.length );
631
632            if ( wrappedValue.length > 0 )
633            {
634                out.write( wrappedValue, 0, wrappedValue.length );
635            }
636        }
637        else
638        {
639            out.writeInt( -1 );
640        }
641
642        // Write the isNormalized flag
643        if ( attributeType != null )
644        {
645            out.writeBoolean( true );
646
647            // Write the normalized value, if not null
648            if ( normalizedValue != null )
649            {
650                out.writeInt( normalizedValue.length );
651
652                if ( normalizedValue.length > 0 )
653                {
654                    out.write( normalizedValue, 0, normalizedValue.length );
655                }
656            }
657            else
658            {
659                out.writeInt( -1 );
660            }
661        }
662        else
663        {
664            out.writeBoolean( false );
665        }
666
667        // The hashCode
668        out.writeInt( h );
669
670        out.flush();
671    }
672
673
674    /**
675     * Dumps binary in hex with label.
676     *
677     * @see Object#toString()
678     */
679    public String toString()
680    {
681        if ( wrappedValue == null )
682        {
683            return "null";
684        }
685        else if ( wrappedValue.length > 16 )
686        {
687            // Just dump the first 16 bytes...
688            byte[] copy = new byte[16];
689
690            System.arraycopy( wrappedValue, 0, copy, 0, 16 );
691
692            return Strings.dumpBytes( copy ) + "...";
693        }
694        else
695        {
696            return Strings.dumpBytes( wrappedValue );
697        }
698    }
699}