001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2009 Sun Microsystems, Inc. 015 * Portions Copyright 2013-2016 ForgeRock AS. 016 */ 017package org.opends.server.api; 018 019import static org.opends.messages.CoreMessages.*; 020import static com.forgerock.opendj.util.StaticUtils.toLowerCase; 021 022import java.util.AbstractMap.SimpleImmutableEntry; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.Iterator; 026import java.util.LinkedHashMap; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Map.Entry; 031import java.util.Set; 032import java.util.concurrent.ConcurrentHashMap; 033import java.util.concurrent.CopyOnWriteArrayList; 034import java.util.concurrent.locks.Lock; 035import java.util.concurrent.locks.ReadWriteLock; 036import java.util.concurrent.locks.ReentrantReadWriteLock; 037 038import net.jcip.annotations.GuardedBy; 039 040import org.forgerock.opendj.ldap.AttributeDescription; 041import org.forgerock.opendj.ldap.ByteSequenceReader; 042import org.forgerock.opendj.ldap.ByteString; 043import org.forgerock.opendj.ldap.ByteStringBuilder; 044import org.forgerock.opendj.ldap.schema.AttributeType; 045import org.forgerock.opendj.ldap.schema.Schema; 046import org.opends.server.core.DirectoryServer; 047import org.opends.server.core.ServerContext; 048import org.opends.server.types.Attribute; 049import org.opends.server.types.AttributeBuilder; 050import org.opends.server.types.Attributes; 051import org.opends.server.types.DirectoryException; 052import org.opends.server.types.ObjectClass; 053import org.opends.server.util.RemoveOnceSDKSchemaIsUsed; 054 055/** 056 * This class provides a utility for interacting with compressed representations 057 * of schema elements. The default implementation does not persist encoded 058 * attributes and object classes. 059 */ 060@org.opends.server.types.PublicAPI( 061 stability = org.opends.server.types.StabilityLevel.UNCOMMITTED, 062 mayInstantiate = false, 063 mayExtend = true, 064 mayInvoke = false) 065public class CompressedSchema 066{ 067 /** Encloses all the encode and decode mappings for attribute and object classes. */ 068 private static final class Mappings 069 { 070 /** Maps encoded representation's ID to its attribute description (the List's index is the ID). */ 071 private final List<AttributeDescription> adDecodeMap = new CopyOnWriteArrayList<>(); 072 /** Maps attribute description to its encoded representation's ID. */ 073 private final Map<AttributeDescription, Integer> adEncodeMap; 074 /** Maps encoded representation's ID to its object class (the List's index is the ID). */ 075 private final List<Map<ObjectClass, String>> ocDecodeMap = new CopyOnWriteArrayList<>(); 076 /** Maps object class to its encoded representation's ID. */ 077 private final Map<Map<ObjectClass, String>, Integer> ocEncodeMap; 078 079 private Mappings() 080 { 081 this.adEncodeMap = new ConcurrentHashMap<>(); 082 this.ocEncodeMap = new ConcurrentHashMap<>(); 083 } 084 085 private Mappings(int adEncodeMapSize, int ocEncodeMapSize) 086 { 087 this.adEncodeMap = new ConcurrentHashMap<>(adEncodeMapSize); 088 this.ocEncodeMap = new ConcurrentHashMap<>(ocEncodeMapSize); 089 } 090 } 091 092 private final ServerContext serverContext; 093 /** Lock to update the maps. */ 094 final ReadWriteLock lock = new ReentrantReadWriteLock(); 095 private final Lock exclusiveLock = lock.writeLock(); 096 private final Lock sharedLock = lock.readLock(); 097 098 /** Schema used to build the compressed information. */ 099 @GuardedBy("lock") 100 private Schema schemaNG; 101 @GuardedBy("lock") 102 private Mappings mappings = new Mappings(); 103 104 /** 105 * Creates a new empty instance of this compressed schema. 106 * 107 * @param serverContext 108 * The server context. 109 */ 110 public CompressedSchema(ServerContext serverContext) 111 { 112 this.serverContext = serverContext; 113 } 114 115 private Mappings getMappings() 116 { 117 sharedLock.lock(); 118 try 119 { 120 return mappings; 121 } 122 finally 123 { 124 sharedLock.unlock(); 125 } 126 } 127 128 private Mappings reloadMappingsIfSchemaChanged(boolean force) 129 { 130 // @RemoveOnceSDKSchemaIsUsed remove the "force" parameter 131 sharedLock.lock(); 132 boolean shared = true; 133 try 134 { 135 Schema currentSchema = serverContext.getSchemaNG(); 136 if (force || schemaNG != currentSchema) 137 { 138 sharedLock.unlock(); 139 exclusiveLock.lock(); 140 shared = false; 141 142 currentSchema = serverContext.getSchemaNG(); 143 if (force || schemaNG != currentSchema) 144 { 145 // build new maps from existing ones 146 Mappings newMappings = new Mappings(mappings.adEncodeMap.size(), mappings.ocEncodeMap.size()); 147 reloadAttributeTypeMaps(mappings, newMappings); 148 reloadObjectClassesMap(mappings, newMappings); 149 150 mappings = newMappings; 151 schemaNG = currentSchema; 152 } 153 } 154 return mappings; 155 } 156 finally 157 { 158 (shared ? sharedLock : exclusiveLock).unlock(); 159 } 160 } 161 162 /** 163 * Reload the attribute types maps. This should be called when schema has changed, because some 164 * types may be out dated. 165 */ 166 private void reloadAttributeTypeMaps(Mappings mappings, Mappings newMappings) 167 { 168 for (Entry<AttributeDescription, Integer> entry : mappings.adEncodeMap.entrySet()) 169 { 170 AttributeDescription ad = entry.getKey(); 171 Integer id = entry.getValue(); 172 loadAttributeToMaps(id, ad.getAttributeType().getNameOrOID(), ad.getOptions(), newMappings); 173 } 174 } 175 176 /** 177 * Reload the object classes maps. This should be called when schema has changed, because some 178 * classes may be out dated. 179 */ 180 private void reloadObjectClassesMap(Mappings mappings, Mappings newMappings) 181 { 182 for (Entry<Map<ObjectClass, String>, Integer> entry : mappings.ocEncodeMap.entrySet()) 183 { 184 Map<ObjectClass, String> ocMap = entry.getKey(); 185 Integer id = entry.getValue(); 186 loadObjectClassesToMaps(id, ocMap.values(), newMappings, false); 187 } 188 } 189 190 /** 191 * Decodes the contents of the provided array as an attribute at the current 192 * position. 193 * 194 * @param reader 195 * The byte string reader containing the encoded entry. 196 * @return The decoded attribute. 197 * @throws DirectoryException 198 * If the attribute could not be decoded properly for some reason. 199 */ 200 public final Attribute decodeAttribute(final ByteSequenceReader reader) 201 throws DirectoryException 202 { 203 // First decode the encoded attribute description id. 204 final int id = decodeId(reader); 205 206 // Before returning the attribute, make sure that the attribute type is not stale. 207 final Mappings mappings = reloadMappingsIfSchemaChanged(false); 208 final AttributeDescription ad = mappings.adDecodeMap.get(id); 209 if (ad == null) 210 { 211 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 212 ERR_COMPRESSEDSCHEMA_UNRECOGNIZED_AD_TOKEN.get(id)); 213 } 214 215 AttributeType attrType = ad.getAttributeType(); 216 217 // Determine the number of values for the attribute. 218 final int numValues = reader.readBERLength(); 219 220 // For the common case of a single value with no options, generate less garbage. 221 if (numValues == 1 && !ad.hasOptions()) 222 { 223 return Attributes.create(attrType, readValue(reader)); 224 } 225 else 226 { 227 // Read the appropriate number of values. 228 final AttributeBuilder builder = new AttributeBuilder(attrType); 229 builder.setOptions(ad.getOptions()); 230 for (int i = 0; i < numValues; i++) 231 { 232 builder.add(readValue(reader)); 233 } 234 return builder.toAttribute(); 235 } 236 } 237 238 private ByteString readValue(final ByteSequenceReader reader) 239 { 240 return reader.readByteSequence(reader.readBERLength()).toByteString(); 241 } 242 243 /** 244 * Decodes an object class set from the provided byte string. 245 * 246 * @param reader 247 * The byte string reader containing the object class set identifier. 248 * @return The decoded object class set. 249 * @throws DirectoryException 250 * If the provided byte string reader cannot be decoded as an object 251 * class set. 252 */ 253 public final Map<ObjectClass, String> decodeObjectClasses( 254 final ByteSequenceReader reader) throws DirectoryException 255 { 256 // First decode the encoded object class id. 257 final int id = decodeId(reader); 258 259 // Look up the object classes. 260 final Mappings mappings = getMappings(); 261 Map<ObjectClass, String> ocMap = mappings.ocDecodeMap.get(id); 262 if (ocMap == null) 263 { 264 // @RemoveOnceSDKSchemaIsUsed remove this first check (check is performed again later) 265 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 266 ERR_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN.get(id)); 267 } 268 // Before returning the object classes, make sure that none of them are stale. 269 boolean forceReload = isAnyObjectClassDirty(ocMap.keySet()); 270 final Mappings newMappings = reloadMappingsIfSchemaChanged(forceReload); 271 if (mappings != newMappings) 272 { 273 ocMap = newMappings.ocDecodeMap.get(id); 274 if (ocMap == null) 275 { 276 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 277 ERR_COMPRESSEDSCHEMA_UNKNOWN_OC_TOKEN.get(id)); 278 } 279 } 280 return ocMap; 281 } 282 283 @RemoveOnceSDKSchemaIsUsed 284 private boolean isAnyObjectClassDirty(Set<ObjectClass> objectClasses) 285 { 286 for (final ObjectClass oc : objectClasses) 287 { 288 if (oc.isDirty()) 289 { 290 return true; 291 } 292 } 293 return false; 294 } 295 296 /** 297 * Encodes the information in the provided attribute to a byte array. 298 * 299 * @param builder 300 * The buffer to encode the attribute to. 301 * @param attribute 302 * The attribute to be encoded. 303 * @throws DirectoryException 304 * If a problem occurs while attempting to determine the appropriate 305 * identifier. 306 */ 307 public final void encodeAttribute(final ByteStringBuilder builder, 308 final Attribute attribute) throws DirectoryException 309 { 310 // Re-use or allocate a new ID. 311 int id = getAttributeId(attribute.getAttributeDescription()); 312 313 // Encode the attribute. 314 final byte[] idBytes = encodeId(id); 315 builder.appendBERLength(idBytes.length); 316 builder.appendBytes(idBytes); 317 builder.appendBERLength(attribute.size()); 318 for (final ByteString v : attribute) 319 { 320 builder.appendBERLength(v.length()); 321 builder.appendBytes(v); 322 } 323 } 324 325 private int getAttributeId(final AttributeDescription ad) throws DirectoryException 326 { 327 // avoid lazy registration races 328 boolean shared = true; 329 sharedLock.lock(); 330 try 331 { 332 Integer id = mappings.adEncodeMap.get(ad); 333 if (id != null) 334 { 335 return id; 336 } 337 338 sharedLock.unlock(); 339 exclusiveLock.lock(); 340 shared = false; 341 342 id = mappings.adEncodeMap.get(ad); 343 if (id == null) 344 { 345 id = mappings.adDecodeMap.size(); 346 mappings.adDecodeMap.add(ad); 347 mappings.adEncodeMap.put(ad, id); 348 storeAttribute(encodeId(id), ad.getAttributeType().getNameOrOID(), ad.getOptions()); 349 } 350 return id; 351 } 352 finally 353 { 354 (shared ? sharedLock : exclusiveLock).unlock(); 355 } 356 } 357 358 /** 359 * Encodes the provided set of object classes to a byte array. If the same set 360 * had been previously encoded, then the cached value will be used. Otherwise, 361 * a new value will be created. 362 * 363 * @param builder 364 * The buffer to encode the object classes to. 365 * @param objectClasses 366 * The set of object classes for which to retrieve the corresponding 367 * byte array token. 368 * @throws DirectoryException 369 * If a problem occurs while attempting to determine the appropriate 370 * identifier. 371 */ 372 public final void encodeObjectClasses(final ByteStringBuilder builder, 373 final Map<ObjectClass, String> objectClasses) throws DirectoryException 374 { 375 // Re-use or allocate a new ID. 376 int id = getObjectClassId(objectClasses); 377 378 // Encode the object classes. 379 final byte[] idBytes = encodeId(id); 380 builder.appendBERLength(idBytes.length); 381 builder.appendBytes(idBytes); 382 } 383 384 private int getObjectClassId(final Map<ObjectClass, String> objectClasses) throws DirectoryException 385 { 386 // avoid lazy registration races 387 boolean shared = true; 388 sharedLock.lock(); 389 try 390 { 391 Integer id = mappings.ocEncodeMap.get(objectClasses); 392 if (id != null) 393 { 394 return id; 395 } 396 397 sharedLock.unlock(); 398 exclusiveLock.lock(); 399 shared = false; 400 401 id = mappings.ocEncodeMap.get(objectClasses); 402 if (id == null) 403 { 404 id = mappings.ocDecodeMap.size(); 405 mappings.ocDecodeMap.add(objectClasses); 406 mappings.ocEncodeMap.put(objectClasses, id); 407 storeObjectClasses(encodeId(id), objectClasses.values()); 408 } 409 return id; 410 } 411 finally 412 { 413 (shared ? sharedLock : exclusiveLock).unlock(); 414 } 415 } 416 417 /** 418 * Returns a view of the encoded attributes in this compressed schema which can be used for saving 419 * the entire content to disk. 420 * <p> 421 * The iterator returned by this method is not thread safe. 422 * 423 * @return A view of the encoded attributes in this compressed schema. 424 */ 425 protected final Iterable<Entry<byte[], Entry<String, Iterable<String>>>> getAllAttributes() 426 { 427 return new Iterable<Entry<byte[], Entry<String, Iterable<String>>>>() 428 { 429 @Override 430 public Iterator<Entry<byte[], Entry<String, Iterable<String>>>> iterator() 431 { 432 return new Iterator<Entry<byte[], Entry<String, Iterable<String>>>>() 433 { 434 private int id; 435 private List<AttributeDescription> adDecodeMap = getMappings().adDecodeMap; 436 437 @Override 438 public boolean hasNext() 439 { 440 return id < adDecodeMap.size(); 441 } 442 443 @Override 444 public Entry<byte[], Entry<String, Iterable<String>>> next() 445 { 446 final byte[] encodedAttribute = encodeId(id); 447 final AttributeDescription ad = adDecodeMap.get(id++); 448 return new SimpleImmutableEntry<byte[], Entry<String, Iterable<String>>>( 449 encodedAttribute, 450 new SimpleImmutableEntry<String, Iterable<String>>( 451 ad.getAttributeType().getNameOrOID(), ad.getOptions())); 452 } 453 454 @Override 455 public void remove() 456 { 457 throw new UnsupportedOperationException(); 458 } 459 }; 460 } 461 }; 462 } 463 464 /** 465 * Returns a view of the encoded object classes in this compressed schema which can be used for 466 * saving the entire content to disk. 467 * <p> 468 * The iterator returned by this method is not thread safe. 469 * 470 * @return A view of the encoded object classes in this compressed schema. 471 */ 472 protected final Iterable<Entry<byte[], Collection<String>>> getAllObjectClasses() 473 { 474 return new Iterable<Entry<byte[], Collection<String>>>() 475 { 476 @Override 477 public Iterator<Entry<byte[], Collection<String>>> iterator() 478 { 479 return new Iterator<Map.Entry<byte[], Collection<String>>>() 480 { 481 private int id; 482 private final List<Map<ObjectClass, String>> ocDecodeMap = getMappings().ocDecodeMap; 483 484 @Override 485 public boolean hasNext() 486 { 487 return id < ocDecodeMap.size(); 488 } 489 490 @Override 491 public Entry<byte[], Collection<String>> next() 492 { 493 final byte[] encodedObjectClasses = encodeId(id); 494 final Map<ObjectClass, String> ocMap = ocDecodeMap.get(id++); 495 return new SimpleImmutableEntry<>(encodedObjectClasses, ocMap.values()); 496 } 497 498 @Override 499 public void remove() 500 { 501 throw new UnsupportedOperationException(); 502 } 503 }; 504 } 505 }; 506 } 507 508 /** 509 * Loads an encoded attribute into this compressed schema. This method may 510 * called by implementations during initialization when loading content from 511 * disk. 512 * 513 * @param encodedAttribute 514 * The encoded attribute description. 515 * @param attributeName 516 * The user provided attribute type name. 517 * @param attributeOptions 518 * The non-null but possibly empty set of attribute options. 519 * @return The attribute type description. 520 */ 521 protected final AttributeDescription loadAttribute( 522 final byte[] encodedAttribute, final String attributeName, 523 final Collection<String> attributeOptions) 524 { 525 final int id = decodeId(encodedAttribute); 526 return loadAttributeToMaps(id, attributeName, attributeOptions, getMappings()); 527 } 528 529 /** 530 * Loads an attribute into provided encode and decode maps, given its id, name, and options. 531 * 532 * @param id 533 * the id computed on the attribute. 534 * @param attributeName 535 * The user provided attribute type name. 536 * @param attributeOptions 537 * The non-null but possibly empty set of attribute options. 538 * @param mappings 539 * attribute description encodeMap and decodeMap maps id to entry 540 * @return The attribute type description. 541 */ 542 private AttributeDescription loadAttributeToMaps(final int id, final String attributeName, 543 final Iterable<String> attributeOptions, final Mappings mappings) 544 { 545 final AttributeType type = DirectoryServer.getAttributeType(attributeName); 546 final Set<String> options = getOptions(attributeOptions); 547 final AttributeDescription ad = AttributeDescription.create(type, options); 548 exclusiveLock.lock(); 549 try 550 { 551 mappings.adEncodeMap.put(ad, id); 552 if (id < mappings.adDecodeMap.size()) 553 { 554 mappings.adDecodeMap.set(id, ad); 555 } 556 else 557 { 558 // Grow the decode array. 559 while (id > mappings.adDecodeMap.size()) 560 { 561 mappings.adDecodeMap.add(null); 562 } 563 mappings.adDecodeMap.add(ad); 564 } 565 return ad; 566 } 567 finally 568 { 569 exclusiveLock.unlock(); 570 } 571 } 572 573 private Set<String> getOptions(final Iterable<String> attributeOptions) 574 { 575 Iterator<String> it = attributeOptions.iterator(); 576 if (!it.hasNext()) 577 { 578 return Collections.emptySet(); 579 } 580 String firstOption = it.next(); 581 if (!it.hasNext()) 582 { 583 return Collections.singleton(firstOption); 584 } 585 LinkedHashSet<String> results = new LinkedHashSet<>(); 586 results.add(firstOption); 587 while (it.hasNext()) 588 { 589 results.add(it.next()); 590 } 591 return results; 592 } 593 594 /** 595 * Loads an encoded object class into this compressed schema. This method may 596 * called by implementations during initialization when loading content from 597 * disk. 598 * 599 * @param encodedObjectClasses 600 * The encoded object classes. 601 * @param objectClassNames 602 * The user provided set of object class names. 603 * @return The object class set. 604 */ 605 protected final Map<ObjectClass, String> loadObjectClasses( 606 final byte[] encodedObjectClasses, 607 final Collection<String> objectClassNames) 608 { 609 final int id = decodeId(encodedObjectClasses); 610 return loadObjectClassesToMaps(id, objectClassNames, mappings, true); 611 } 612 613 /** 614 * Loads a set of object classes into provided encode and decode maps, given the id and set of 615 * names. 616 * 617 * @param id 618 * the id computed on the object classes set. 619 * @param objectClassNames 620 * The user provided set of object class names. 621 * @param mappings 622 * .ocEncodeMap maps id to entry 623 * @param mappings 624 * .ocDecodeMap maps entry to id 625 * @param sync 626 * indicates if update of maps should be synchronized 627 * @return The object class set. 628 */ 629 private final Map<ObjectClass, String> loadObjectClassesToMaps(int id, final Collection<String> objectClassNames, 630 Mappings mappings, boolean sync) 631 { 632 final LinkedHashMap<ObjectClass, String> ocMap = new LinkedHashMap<>(objectClassNames.size()); 633 for (final String name : objectClassNames) 634 { 635 final String lowerName = toLowerCase(name); 636 final ObjectClass oc = DirectoryServer.getObjectClass(lowerName, true); 637 ocMap.put(oc, name); 638 } 639 if (sync) 640 { 641 exclusiveLock.lock(); 642 try 643 { 644 updateObjectClassesMaps(id, mappings, ocMap); 645 } 646 finally 647 { 648 exclusiveLock.unlock(); 649 } 650 } 651 else 652 { 653 updateObjectClassesMaps(id, mappings, ocMap); 654 } 655 return ocMap; 656 } 657 658 private void updateObjectClassesMaps(int id, Mappings mappings, LinkedHashMap<ObjectClass, String> ocMap) 659 { 660 mappings.ocEncodeMap.put(ocMap, id); 661 if (id < mappings.ocDecodeMap.size()) 662 { 663 mappings.ocDecodeMap.set(id, ocMap); 664 } 665 else 666 { 667 // Grow the decode array. 668 while (id > mappings.ocDecodeMap.size()) 669 { 670 mappings.ocDecodeMap.add(null); 671 } 672 mappings.ocDecodeMap.add(ocMap); 673 } 674 } 675 676 /** 677 * Persists the provided encoded attribute. The default implementation is to 678 * do nothing. Calls to this method are synchronized, so implementations can 679 * assume that this method is not being called by other threads. Note that 680 * this method is not thread-safe with respect to 681 * {@link #storeObjectClasses(byte[], Collection)}. 682 * 683 * @param encodedAttribute 684 * The encoded attribute description. 685 * @param attributeName 686 * The user provided attribute type name. 687 * @param attributeOptions 688 * The non-null but possibly empty set of attribute options. 689 * @throws DirectoryException 690 * If an error occurred while persisting the encoded attribute. 691 */ 692 protected void storeAttribute(final byte[] encodedAttribute, 693 final String attributeName, final Iterable<String> attributeOptions) 694 throws DirectoryException 695 { 696 // Do nothing by default. 697 } 698 699 /** 700 * Persists the provided encoded object classes. The default implementation is 701 * to do nothing. Calls to this method are synchronized, so implementations 702 * can assume that this method is not being called by other threads. Note that 703 * this method is not thread-safe with respect to 704 * {@link #storeAttribute(byte[], String, Iterable)}. 705 * 706 * @param encodedObjectClasses 707 * The encoded object classes. 708 * @param objectClassNames 709 * The user provided set of object class names. 710 * @throws DirectoryException 711 * If an error occurred while persisting the encoded object classes. 712 */ 713 protected void storeObjectClasses(final byte[] encodedObjectClasses, 714 final Collection<String> objectClassNames) throws DirectoryException 715 { 716 // Do nothing by default. 717 } 718 719 /** 720 * Decodes the provided encoded schema element ID. 721 * 722 * @param idBytes 723 * The encoded schema element ID. 724 * @return The schema element ID. 725 */ 726 private int decodeId(final byte[] idBytes) 727 { 728 int id = 0; 729 for (final byte b : idBytes) 730 { 731 id <<= 8; 732 id |= b & 0xFF; 733 } 734 return id - 1; // Subtract 1 to compensate for old behavior. 735 } 736 737 private int decodeId(final ByteSequenceReader reader) 738 { 739 final int length = reader.readBERLength(); 740 final byte[] idBytes = new byte[length]; 741 reader.readBytes(idBytes); 742 return decodeId(idBytes); 743 } 744 745 /** 746 * Encodes the provided schema element ID. 747 * 748 * @param id 749 * The schema element ID. 750 * @return The encoded schema element ID. 751 */ 752 private byte[] encodeId(final int id) 753 { 754 final int value = id + 1; // Add 1 to compensate for old behavior. 755 final byte[] idBytes; 756 if (value <= 0xFF) 757 { 758 idBytes = new byte[1]; 759 idBytes[0] = (byte) (value & 0xFF); 760 } 761 else if (value <= 0xFFFF) 762 { 763 idBytes = new byte[2]; 764 idBytes[0] = (byte) ((value >> 8) & 0xFF); 765 idBytes[1] = (byte) (value & 0xFF); 766 } 767 else if (value <= 0xFFFFFF) 768 { 769 idBytes = new byte[3]; 770 idBytes[0] = (byte) ((value >> 16) & 0xFF); 771 idBytes[1] = (byte) ((value >> 8) & 0xFF); 772 idBytes[2] = (byte) (value & 0xFF); 773 } 774 else 775 { 776 idBytes = new byte[4]; 777 idBytes[0] = (byte) ((value >> 24) & 0xFF); 778 idBytes[1] = (byte) ((value >> 16) & 0xFF); 779 idBytes[2] = (byte) ((value >> 8) & 0xFF); 780 idBytes[3] = (byte) (value & 0xFF); 781 } 782 return idBytes; 783 } 784}