1 /*
   2  * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.internal.module;
  27 
  28 import java.lang.module.ModuleDescriptor;
  29 import java.lang.module.ModuleDescriptor.Requires;
  30 import java.lang.module.ModuleDescriptor.Exports;
  31 import java.lang.module.ModuleDescriptor.Opens;
  32 import java.lang.module.ModuleDescriptor.Provides;
  33 import java.lang.module.ModuleDescriptor.Version;
  34 import java.util.ArrayList;
  35 import java.util.Collections;
  36 import java.util.HashMap;
  37 import java.util.HashSet;
  38 import java.util.List;
  39 import java.util.Map;
  40 import java.util.Set;
  41 
  42 import jdk.internal.misc.JavaLangModuleAccess;
  43 import jdk.internal.misc.SharedSecrets;
  44 import jdk.internal.org.objectweb.asm.Attribute;
  45 import jdk.internal.org.objectweb.asm.ByteVector;
  46 import jdk.internal.org.objectweb.asm.ClassReader;
  47 import jdk.internal.org.objectweb.asm.ClassWriter;
  48 import jdk.internal.org.objectweb.asm.Label;
  49 import static jdk.internal.module.ClassFileConstants.*;
  50 
  51 
  52 /**
  53  * Provides ASM implementations of {@code Attribute} to read and write the
  54  * class file attributes in a module-info class file.
  55  */
  56 
  57 public final class ClassFileAttributes {
  58 
  59     private ClassFileAttributes() { }
  60 
  61     /**
  62      * Module_attribute {
  63      *   // See lang-vm.html for details.
  64      * }
  65      */
  66     public static class ModuleAttribute extends Attribute {
  67         private static final JavaLangModuleAccess JLMA
  68             = SharedSecrets.getJavaLangModuleAccess();
  69 
  70         private ModuleDescriptor descriptor;
  71 
  72         public ModuleAttribute(ModuleDescriptor descriptor) {
  73             super(MODULE);
  74             this.descriptor = descriptor;
  75         }
  76 
  77         public ModuleAttribute() {
  78             super(MODULE);
  79         }
  80 
  81         @Override
  82         protected Attribute read(ClassReader cr,
  83                                  int off,
  84                                  int len,
  85                                  char[] buf,
  86                                  int codeOff,
  87                                  Label[] labels)
  88         {
  89             ModuleAttribute attr = new ModuleAttribute();
  90 
  91             // module_name
  92             String mn = cr.readUTF8(off, buf).replace('/', '.');
  93             off += 2;
  94 
  95             // module_flags
  96             int module_flags = cr.readUnsignedShort(off);
  97             boolean open = ((module_flags & ACC_OPEN) != 0);
  98             off += 2;
  99 
 100             ModuleDescriptor.Builder builder;
 101             if (open) {
 102                 builder = JLMA.newOpenModuleBuilder(mn, false);
 103             } else {
 104                 builder = JLMA.newModuleBuilder(mn, false);
 105             }
 106 
 107             // requires_count and requires[requires_count]
 108             int requires_count = cr.readUnsignedShort(off);
 109             off += 2;
 110             for (int i=0; i<requires_count; i++) {
 111                 String dn = cr.readUTF8(off, buf).replace('/', '.');
 112                 int flags = cr.readUnsignedShort(off + 2);
 113                 Set<Requires.Modifier> mods;
 114                 if (flags == 0) {
 115                     mods = Collections.emptySet();
 116                 } else {
 117                     mods = new HashSet<>();
 118                     if ((flags & ACC_TRANSITIVE) != 0)
 119                         mods.add(Requires.Modifier.TRANSITIVE);
 120                     if ((flags & ACC_STATIC_PHASE) != 0)
 121                         mods.add(Requires.Modifier.STATIC);
 122                     if ((flags & ACC_SYNTHETIC) != 0)
 123                         mods.add(Requires.Modifier.SYNTHETIC);
 124                     if ((flags & ACC_MANDATED) != 0)
 125                         mods.add(Requires.Modifier.MANDATED);
 126                 }
 127                 builder.requires(mods, dn);
 128                 off += 4;
 129             }
 130 
 131             // exports_count and exports[exports_count]
 132             int exports_count = cr.readUnsignedShort(off);
 133             off += 2;
 134             if (exports_count > 0) {
 135                 for (int i=0; i<exports_count; i++) {
 136                     String pkg = cr.readUTF8(off, buf).replace('/', '.');
 137                     off += 2;
 138 
 139                     int flags = cr.readUnsignedShort(off);
 140                     off += 2;
 141                     Set<Exports.Modifier> mods;
 142                     if (flags == 0) {
 143                         mods = Collections.emptySet();
 144                     } else {
 145                         mods = new HashSet<>();
 146                         if ((flags & ACC_SYNTHETIC) != 0)
 147                             mods.add(Exports.Modifier.SYNTHETIC);
 148                         if ((flags & ACC_MANDATED) != 0)
 149                             mods.add(Exports.Modifier.MANDATED);
 150                     }
 151 
 152                     int exports_to_count = cr.readUnsignedShort(off);
 153                     off += 2;
 154                     if (exports_to_count > 0) {
 155                         Set<String> targets = new HashSet<>();
 156                         for (int j=0; j<exports_to_count; j++) {
 157                             String t = cr.readUTF8(off, buf).replace('/', '.');
 158                             off += 2;
 159                             targets.add(t);
 160                         }
 161                         builder.exports(mods, pkg, targets);
 162                     } else {
 163                         builder.exports(mods, pkg);
 164                     }
 165                 }
 166             }
 167 
 168             // opens_count and opens[opens_count]
 169             int open_count = cr.readUnsignedShort(off);
 170             off += 2;
 171             if (open_count > 0) {
 172                 for (int i=0; i<open_count; i++) {
 173                     String pkg = cr.readUTF8(off, buf).replace('/', '.');
 174                     off += 2;
 175 
 176                     int flags = cr.readUnsignedShort(off);
 177                     off += 2;
 178                     Set<Opens.Modifier> mods;
 179                     if (flags == 0) {
 180                         mods = Collections.emptySet();
 181                     } else {
 182                         mods = new HashSet<>();
 183                         if ((flags & ACC_SYNTHETIC) != 0)
 184                             mods.add(Opens.Modifier.SYNTHETIC);
 185                         if ((flags & ACC_MANDATED) != 0)
 186                             mods.add(Opens.Modifier.MANDATED);
 187                     }
 188 
 189                     int opens_to_count = cr.readUnsignedShort(off);
 190                     off += 2;
 191                     if (opens_to_count > 0) {
 192                         Set<String> targets = new HashSet<>();
 193                         for (int j=0; j<opens_to_count; j++) {
 194                             String t = cr.readUTF8(off, buf).replace('/', '.');
 195                             off += 2;
 196                             targets.add(t);
 197                         }
 198                         builder.opens(mods, pkg, targets);
 199                     } else {
 200                         builder.opens(mods, pkg);
 201                     }
 202                 }
 203             }
 204 
 205             // uses_count and uses_index[uses_count]
 206             int uses_count = cr.readUnsignedShort(off);
 207             off += 2;
 208             if (uses_count > 0) {
 209                 for (int i=0; i<uses_count; i++) {
 210                     String sn = cr.readClass(off, buf).replace('/', '.');
 211                     builder.uses(sn);
 212                     off += 2;
 213                 }
 214             }
 215 
 216             // provides_count and provides[provides_count]
 217             int provides_count = cr.readUnsignedShort(off);
 218             off += 2;
 219             if (provides_count > 0) {
 220                 for (int i=0; i<provides_count; i++) {
 221                     String service = cr.readClass(off, buf).replace('/', '.');
 222                     off += 2;
 223                     int with_count = cr.readUnsignedShort(off);
 224                     off += 2;
 225                     List<String> providers = new ArrayList<>();
 226                     for (int j=0; j<with_count; j++) {
 227                         String cn = cr.readClass(off, buf).replace('/', '.');
 228                         off += 2;
 229                         providers.add(cn);
 230                     }
 231                     builder.provides(service, providers);
 232                 }
 233             }
 234 
 235             attr.descriptor = builder.build();
 236             return attr;
 237         }
 238 
 239         @Override
 240         protected ByteVector write(ClassWriter cw,
 241                                    byte[] code,
 242                                    int len,
 243                                    int maxStack,
 244                                    int maxLocals)
 245         {
 246             assert descriptor != null;
 247             ByteVector attr = new ByteVector();
 248 
 249             // module_name
 250             String mn = descriptor.name();
 251             int module_name_index = cw.newUTF8(mn.replace('.', '/'));
 252             attr.putShort(module_name_index);
 253 
 254             // module_flags
 255             int module_flags = 0;
 256             if (descriptor.isOpen())
 257                 module_flags |= ACC_OPEN;
 258             if (descriptor.isSynthetic())
 259                 module_flags |= ACC_SYNTHETIC;
 260             attr.putShort(module_flags);
 261 
 262             // requires_count
 263             attr.putShort(descriptor.requires().size());
 264 
 265             // requires[requires_count]
 266             for (Requires md : descriptor.requires()) {
 267                 String dn = md.name();
 268                 int flags = 0;
 269                 if (md.modifiers().contains(Requires.Modifier.TRANSITIVE))
 270                     flags |= ACC_TRANSITIVE;
 271                 if (md.modifiers().contains(Requires.Modifier.STATIC))
 272                     flags |= ACC_STATIC_PHASE;
 273                 if (md.modifiers().contains(Requires.Modifier.SYNTHETIC))
 274                     flags |= ACC_SYNTHETIC;
 275                 if (md.modifiers().contains(Requires.Modifier.MANDATED))
 276                     flags |= ACC_MANDATED;
 277                 int index = cw.newUTF8(dn.replace('.', '/'));
 278                 attr.putShort(index);
 279                 attr.putShort(flags);
 280             }
 281 
 282             // exports_count and exports[exports_count];
 283             attr.putShort(descriptor.exports().size());
 284             for (Exports e : descriptor.exports()) {
 285                 String pkg = e.source().replace('.', '/');
 286                 attr.putShort(cw.newUTF8(pkg));
 287 
 288                 int flags = 0;
 289                 if (e.modifiers().contains(Exports.Modifier.SYNTHETIC))
 290                     flags |= ACC_SYNTHETIC;
 291                 if (e.modifiers().contains(Exports.Modifier.MANDATED))
 292                     flags |= ACC_MANDATED;
 293                 attr.putShort(flags);
 294 
 295                 if (e.isQualified()) {
 296                     Set<String> ts = e.targets();
 297                     attr.putShort(ts.size());
 298                     ts.forEach(t -> attr.putShort(cw.newUTF8(t.replace('.', '/'))));
 299                 } else {
 300                     attr.putShort(0);
 301                 }
 302             }
 303 
 304 
 305             // opens_counts and opens[opens_counts]
 306             attr.putShort(descriptor.opens().size());
 307             for (Opens obj : descriptor.opens()) {
 308                 String pkg = obj.source().replace('.', '/');
 309                 attr.putShort(cw.newUTF8(pkg));
 310 
 311                 int flags = 0;
 312                 if (obj.modifiers().contains(Opens.Modifier.SYNTHETIC))
 313                     flags |= ACC_SYNTHETIC;
 314                 if (obj.modifiers().contains(Opens.Modifier.MANDATED))
 315                     flags |= ACC_MANDATED;
 316                 attr.putShort(flags);
 317 
 318                 if (obj.isQualified()) {
 319                     Set<String> ts = obj.targets();
 320                     attr.putShort(ts.size());
 321                     ts.forEach(t -> attr.putShort(cw.newUTF8(t.replace('.', '/'))));
 322                 } else {
 323                     attr.putShort(0);
 324                 }
 325             }
 326 
 327             // uses_count and uses_index[uses_count]
 328             if (descriptor.uses().isEmpty()) {
 329                 attr.putShort(0);
 330             } else {
 331                 attr.putShort(descriptor.uses().size());
 332                 for (String s : descriptor.uses()) {
 333                     String service = s.replace('.', '/');
 334                     int index = cw.newClass(service);
 335                     attr.putShort(index);
 336                 }
 337             }
 338 
 339             // provides_count and provides[provides_count]
 340             if (descriptor.provides().isEmpty()) {
 341                 attr.putShort(0);
 342             } else {
 343                 attr.putShort(descriptor.provides().size());
 344                 for (Provides p : descriptor.provides()) {
 345                     String service = p.service().replace('.', '/');
 346                     attr.putShort(cw.newClass(service));
 347                     int with_count = p.providers().size();
 348                     attr.putShort(with_count);
 349                     for (String provider : p.providers()) {
 350                         attr.putShort(cw.newClass(provider.replace('.', '/')));
 351                     }
 352                 }
 353             }
 354 
 355             return attr;
 356         }
 357     }
 358 
 359     /**
 360      * ModulePackages attribute.
 361      *
 362      * <pre> {@code
 363      *
 364      * ModulePackages_attribute {
 365      *   // index to CONSTANT_utf8_info structure in constant pool representing
 366      *   // the string "ModulePackages"
 367      *   u2 attribute_name_index;
 368      *   u4 attribute_length;
 369      *
 370      *   // the number of entries in the packages table
 371      *   u2 packages_count;
 372      *   { // index to CONSTANT_CONSTANT_utf8_info structure with the package name
 373      *     u2 package_index
 374      *   } packages[package_count];
 375      *
 376      * }</pre>
 377      */
 378     public static class ModulePackagesAttribute extends Attribute {
 379         private final Set<String> packages;
 380 
 381         public ModulePackagesAttribute(Set<String> packages) {
 382             super(MODULE_PACKAGES);
 383             this.packages = packages;
 384         }
 385 
 386         public ModulePackagesAttribute() {
 387             this(null);
 388         }
 389 
 390         @Override
 391         protected Attribute read(ClassReader cr,
 392                                  int off,
 393                                  int len,
 394                                  char[] buf,
 395                                  int codeOff,
 396                                  Label[] labels)
 397         {
 398             // package count
 399             int package_count = cr.readUnsignedShort(off);
 400             off += 2;
 401 
 402             // packages
 403             Set<String> packages = new HashSet<>();
 404             for (int i=0; i<package_count; i++) {
 405                 String pkg = cr.readUTF8(off, buf).replace('/', '.');
 406                 packages.add(pkg);
 407                 off += 2;
 408             }
 409 
 410             return new ModulePackagesAttribute(packages);
 411         }
 412 
 413         @Override
 414         protected ByteVector write(ClassWriter cw,
 415                                    byte[] code,
 416                                    int len,
 417                                    int maxStack,
 418                                    int maxLocals)
 419         {
 420             assert packages != null;
 421 
 422             ByteVector attr = new ByteVector();
 423 
 424             // package_count
 425             attr.putShort(packages.size());
 426 
 427             // packages
 428             packages.stream()
 429                 .map(p -> p.replace('.', '/'))
 430                 .forEach(p -> attr.putShort(cw.newUTF8(p)));
 431 
 432             return attr;
 433         }
 434 
 435     }
 436 
 437     /**
 438      * ModuleVersion attribute.
 439      *
 440      * <pre> {@code
 441      *
 442      * ModuleVersion_attribute {
 443      *   // index to CONSTANT_utf8_info structure in constant pool representing
 444      *   // the string "ModuleVersion"
 445      *   u2 attribute_name_index;
 446      *   u4 attribute_length;
 447      *
 448      *   // index to CONSTANT_CONSTANT_utf8_info structure with the version
 449      *   u2 version_index;
 450      * }
 451      *
 452      * } </pre>
 453      */
 454     public static class ModuleVersionAttribute extends Attribute {
 455         private final Version version;
 456 
 457         public ModuleVersionAttribute(Version version) {
 458             super(MODULE_VERSION);
 459             this.version = version;
 460         }
 461 
 462         public ModuleVersionAttribute() {
 463             this(null);
 464         }
 465 
 466         @Override
 467         protected Attribute read(ClassReader cr,
 468                                  int off,
 469                                  int len,
 470                                  char[] buf,
 471                                  int codeOff,
 472                                  Label[] labels)
 473         {
 474             String value = cr.readUTF8(off, buf);
 475             return new ModuleVersionAttribute(Version.parse(value));
 476         }
 477 
 478         @Override
 479         protected ByteVector write(ClassWriter cw,
 480                                    byte[] code,
 481                                    int len,
 482                                    int maxStack,
 483                                    int maxLocals)
 484         {
 485             ByteVector attr = new ByteVector();
 486             int index = cw.newUTF8(version.toString());
 487             attr.putShort(index);
 488             return attr;
 489         }
 490     }
 491 
 492     /**
 493      * ModuleMainClass attribute.
 494      *
 495      * <pre> {@code
 496      *
 497      * MainClass_attribute {
 498      *   // index to CONSTANT_utf8_info structure in constant pool representing
 499      *   // the string "ModuleMainClass"
 500      *   u2 attribute_name_index;
 501      *   u4 attribute_length;
 502      *
 503      *   // index to CONSTANT_Class_info structure with the main class name
 504      *   u2 main_class_index;
 505      * }
 506      *
 507      * } </pre>
 508      */
 509     public static class ModuleMainClassAttribute extends Attribute {
 510         private final String mainClass;
 511 
 512         public ModuleMainClassAttribute(String mainClass) {
 513             super(MODULE_MAIN_CLASS);
 514             this.mainClass = mainClass;
 515         }
 516 
 517         public ModuleMainClassAttribute() {
 518             this(null);
 519         }
 520 
 521         @Override
 522         protected Attribute read(ClassReader cr,
 523                                  int off,
 524                                  int len,
 525                                  char[] buf,
 526                                  int codeOff,
 527                                  Label[] labels)
 528         {
 529             String value = cr.readClass(off, buf);
 530             return new ModuleMainClassAttribute(value);
 531         }
 532 
 533         @Override
 534         protected ByteVector write(ClassWriter cw,
 535                                    byte[] code,
 536                                    int len,
 537                                    int maxStack,
 538                                    int maxLocals)
 539         {
 540             ByteVector attr = new ByteVector();
 541             int index = cw.newClass(mainClass);
 542             attr.putShort(index);
 543             return attr;
 544         }
 545     }
 546 
 547     /**
 548      * ModuleTarget attribute.
 549      *
 550      * <pre> {@code
 551      *
 552      * TargetPlatform_attribute {
 553      *   // index to CONSTANT_utf8_info structure in constant pool representing
 554      *   // the string "ModuleTarget"
 555      *   u2 attribute_name_index;
 556      *   u4 attribute_length;
 557      *
 558      *   // index to CONSTANT_CONSTANT_utf8_info structure with the OS name
 559      *   u2 os_name_index;
 560      *   // index to CONSTANT_CONSTANT_utf8_info structure with the OS arch
 561      *   u2 os_arch_index
 562      *   // index to CONSTANT_CONSTANT_utf8_info structure with the OS version
 563      *   u2 os_version_index;
 564      * }
 565      *
 566      * } </pre>
 567      */
 568     public static class ModuleTargetAttribute extends Attribute {
 569         private final String osName;
 570         private final String osArch;
 571         private final String osVersion;
 572 
 573         public ModuleTargetAttribute(String osName, String osArch, String osVersion) {
 574             super(MODULE_TARGET);
 575             this.osName = osName;
 576             this.osArch = osArch;
 577             this.osVersion = osVersion;
 578         }
 579 
 580         public ModuleTargetAttribute() {
 581             this(null, null, null);
 582         }
 583 
 584         @Override
 585         protected Attribute read(ClassReader cr,
 586                                  int off,
 587                                  int len,
 588                                  char[] buf,
 589                                  int codeOff,
 590                                  Label[] labels)
 591         {
 592 
 593             String osName = null;
 594             String osArch = null;
 595             String osVersion = null;
 596 
 597             int name_index = cr.readUnsignedShort(off);
 598             if (name_index != 0)
 599                 osName = cr.readUTF8(off, buf);
 600             off += 2;
 601 
 602             int arch_index = cr.readUnsignedShort(off);
 603             if (arch_index != 0)
 604                 osArch = cr.readUTF8(off, buf);
 605             off += 2;
 606 
 607             int version_index = cr.readUnsignedShort(off);
 608             if (version_index != 0)
 609                 osVersion = cr.readUTF8(off, buf);
 610             off += 2;
 611 
 612             return new ModuleTargetAttribute(osName, osArch, osVersion);
 613         }
 614 
 615         @Override
 616         protected ByteVector write(ClassWriter cw,
 617                                    byte[] code,
 618                                    int len,
 619                                    int maxStack,
 620                                    int maxLocals)
 621         {
 622             ByteVector attr = new ByteVector();
 623 
 624             int name_index = 0;
 625             if (osName != null && osName.length() > 0)
 626                 name_index = cw.newUTF8(osName);
 627             attr.putShort(name_index);
 628 
 629             int arch_index = 0;
 630             if (osArch != null && osArch.length() > 0)
 631                 arch_index = cw.newUTF8(osArch);
 632             attr.putShort(arch_index);
 633 
 634             int version_index = 0;
 635             if (osVersion != null && osVersion.length() > 0)
 636                 version_index = cw.newUTF8(osVersion);
 637             attr.putShort(version_index);
 638 
 639             return attr;
 640         }
 641     }
 642 
 643     /**
 644      * ModuleHashes attribute.
 645      *
 646      * <pre> {@code
 647      *
 648      * ModuleHashes_attribute {
 649      *   // index to CONSTANT_utf8_info structure in constant pool representing
 650      *   // the string "ModuleHashes"
 651      *   u2 attribute_name_index;
 652      *   u4 attribute_length;
 653      *
 654      *   // index to CONSTANT_utf8_info structure with algorithm name
 655      *   u2 algorithm_index;
 656      *
 657      *   // the number of entries in the hashes table
 658      *   u2 hashes_count;
 659      *   {   u2 module_name_index
 660      *       u2 hash_length;
 661      *       u1 hash[hash_length];
 662      *   } hashes[hashes_count];
 663      *
 664      * } </pre>
 665      */
 666     static class ModuleHashesAttribute extends Attribute {
 667         private final ModuleHashes hashes;
 668 
 669         ModuleHashesAttribute(ModuleHashes hashes) {
 670             super(MODULE_HASHES);
 671             this.hashes = hashes;
 672         }
 673 
 674         ModuleHashesAttribute() {
 675             this(null);
 676         }
 677 
 678         @Override
 679         protected Attribute read(ClassReader cr,
 680                                  int off,
 681                                  int len,
 682                                  char[] buf,
 683                                  int codeOff,
 684                                  Label[] labels)
 685         {
 686             String algorithm = cr.readUTF8(off, buf);
 687             off += 2;
 688 
 689             int hashes_count = cr.readUnsignedShort(off);
 690             off += 2;
 691 
 692             Map<String, byte[]> map = new HashMap<>();
 693             for (int i=0; i<hashes_count; i++) {
 694                 String mn = cr.readUTF8(off, buf).replace('/', '.');
 695                 off += 2;
 696 
 697                 int hash_length = cr.readUnsignedShort(off);
 698                 off += 2;
 699                 byte[] hash = new byte[hash_length];
 700                 for (int j=0; j<hash_length; j++) {
 701                     hash[j] = (byte) (0xff & cr.readByte(off+j));
 702                 }
 703                 off += hash_length;
 704 
 705                 map.put(mn, hash);
 706             }
 707 
 708             ModuleHashes hashes = new ModuleHashes(algorithm, map);
 709 
 710             return new ModuleHashesAttribute(hashes);
 711         }
 712 
 713         @Override
 714         protected ByteVector write(ClassWriter cw,
 715                                    byte[] code,
 716                                    int len,
 717                                    int maxStack,
 718                                    int maxLocals)
 719         {
 720             ByteVector attr = new ByteVector();
 721 
 722             int index = cw.newUTF8(hashes.algorithm());
 723             attr.putShort(index);
 724 
 725             Set<String> names = hashes.names();
 726             attr.putShort(names.size());
 727 
 728             for (String mn : names) {
 729                 byte[] hash = hashes.hashFor(mn);
 730                 assert hash != null;
 731                 attr.putShort(cw.newUTF8(mn.replace('.', '/')));
 732 
 733                 attr.putShort(hash.length);
 734                 for (byte b: hash) {
 735                     attr.putByte(b);
 736                 }
 737             }
 738 
 739             return attr;
 740         }
 741     }
 742 
 743 }