jdk/src/jdk.jartool/share/classes/sun/tools/jar/Main.java
changeset 45004 ea3137042a61
parent 44360 a83651664fa3
equal deleted inserted replaced
44789:73fd39e0702e 45004:ea3137042a61
    69 
    69 
    70 import static jdk.internal.util.jar.JarIndex.INDEX_NAME;
    70 import static jdk.internal.util.jar.JarIndex.INDEX_NAME;
    71 import static java.util.jar.JarFile.MANIFEST_NAME;
    71 import static java.util.jar.JarFile.MANIFEST_NAME;
    72 import static java.util.stream.Collectors.joining;
    72 import static java.util.stream.Collectors.joining;
    73 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
    73 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
       
    74 import static sun.tools.jar.Validator.ENTRYNAME_COMPARATOR;
    74 
    75 
    75 /**
    76 /**
    76  * This class implements a simple utility for creating files in the JAR
    77  * This class implements a simple utility for creating files in the JAR
    77  * (Java Archive) file format. The JAR format is based on the ZIP file
    78  * (Java Archive) file format. The JAR format is based on the ZIP file
    78  * format, with optional meta-information stored in a MANIFEST entry.
    79  * format, with optional meta-information stored in a MANIFEST entry.
   129     Map<Integer,String[]> filesMap = new HashMap<>();
   130     Map<Integer,String[]> filesMap = new HashMap<>();
   130 
   131 
   131     // Do we think this is a multi-release jar?  Set to true
   132     // Do we think this is a multi-release jar?  Set to true
   132     // if --release option found followed by at least file
   133     // if --release option found followed by at least file
   133     boolean isMultiRelease;
   134     boolean isMultiRelease;
       
   135 
       
   136     // The last parsed --release value, if any. Used in conjunction with
       
   137     // "-d,--describe-module" to select the operative module descriptor.
       
   138     int releaseValue = -1;
   134 
   139 
   135     /*
   140     /*
   136      * cflag: create
   141      * cflag: create
   137      * uflag: update
   142      * uflag: update
   138      * xflag: xtract
   143      * xflag: xtract
   411                     try (ZipFile zf = new ZipFile(fname)) {
   416                     try (ZipFile zf = new ZipFile(fname)) {
   412                         found = describeModule(zf);
   417                         found = describeModule(zf);
   413                     }
   418                     }
   414                 } else {
   419                 } else {
   415                     try (FileInputStream fin = new FileInputStream(FileDescriptor.in)) {
   420                     try (FileInputStream fin = new FileInputStream(FileDescriptor.in)) {
   416                         found = describeModule(fin);
   421                         found = describeModuleFromStream(fin);
   417                     }
   422                     }
   418                 }
   423                 }
   419                 if (!found)
   424                 if (!found)
   420                     error(getMsg("error.module.descriptor.not.found"));
   425                     error(getMsg("error.module.descriptor.not.found"));
   421             }
   426             }
   602         }
   607         }
   603 
   608 
   604         /* parse file arguments */
   609         /* parse file arguments */
   605         int n = args.length - count;
   610         int n = args.length - count;
   606         if (n > 0) {
   611         if (n > 0) {
   607             if (dflag) {
       
   608                 // "--describe-module/-d" does not require file argument(s)
       
   609                 usageError(formatMsg("error.bad.dflag", args[count]));
       
   610                 return false;
       
   611             }
       
   612             int version = BASE_VERSION;
   612             int version = BASE_VERSION;
   613             int k = 0;
   613             int k = 0;
   614             String[] nameBuf = new String[n];
   614             String[] nameBuf = new String[n];
   615             pathsMap.put(version, new HashSet<>());
   615             pathsMap.put(version, new HashSet<>());
   616             try {
   616             try {
   617                 for (int i = count; i < args.length; i++) {
   617                 for (int i = count; i < args.length; i++) {
   618                     if (args[i].equals("-C")) {
   618                     if (args[i].equals("-C")) {
       
   619                         if (dflag) {
       
   620                             // "--describe-module/-d" does not require file argument(s),
       
   621                             // but does accept --release
       
   622                             usageError(getMsg("error.bad.dflag"));
       
   623                             return false;
       
   624                         }
   619                         /* change the directory */
   625                         /* change the directory */
   620                         String dir = args[++i];
   626                         String dir = args[++i];
   621                         dir = (dir.endsWith(File.separator) ?
   627                         dir = (dir.endsWith(File.separator) ?
   622                                dir : (dir + File.separator));
   628                                dir : (dir + File.separator));
   623                         dir = dir.replace(File.separatorChar, '/');
   629                         dir = dir.replace(File.separatorChar, '/');
   647                         }
   653                         }
   648                         // reset the counters and start with the new version number
   654                         // reset the counters and start with the new version number
   649                         k = 0;
   655                         k = 0;
   650                         nameBuf = new String[n];
   656                         nameBuf = new String[n];
   651                         version = v;
   657                         version = v;
       
   658                         releaseValue = version;
   652                         pathsMap.put(version, new HashSet<>());
   659                         pathsMap.put(version, new HashSet<>());
   653                     } else {
   660                     } else {
       
   661                         if (dflag) {
       
   662                             // "--describe-module/-d" does not require file argument(s),
       
   663                             // but does accept --release
       
   664                             usageError(getMsg("error.bad.dflag"));
       
   665                             return false;
       
   666                         }
   654                         nameBuf[k++] = args[i];
   667                         nameBuf[k++] = args[i];
   655                     }
   668                     }
   656                 }
   669                 }
   657             } catch (ArrayIndexOutOfBoundsException e) {
   670             } catch (ArrayIndexOutOfBoundsException e) {
   658                 usageError(getMsg("error.bad.file.arg"));
   671                 usageError(getMsg("error.bad.file.arg"));
   754     /**
   767     /**
   755      * Expands list of files to process into full list of all files that
   768      * Expands list of files to process into full list of all files that
   756      * can be found by recursively descending directories.
   769      * can be found by recursively descending directories.
   757      *
   770      *
   758      * @param dir    parent directory
   771      * @param dir    parent directory
   759      * @param file s list of files to expand
   772      * @param files  list of files to expand
   760      * @param cpaths set of directories specified by -C option for the files
   773      * @param cpaths set of directories specified by -C option for the files
   761      * @throws IOException if an I/O error occurs
   774      * @throws IOException if an I/O error occurs
   762      */
   775      */
   763     private void expand(File dir, String[] files, Set<String> cpaths, int version)
   776     private void expand(File dir, String[] files, Set<String> cpaths, int version)
   764         throws IOException
   777         throws IOException
  1719         return tmpfile;
  1732         return tmpfile;
  1720     }
  1733     }
  1721 
  1734 
  1722     // Modular jar support
  1735     // Modular jar support
  1723 
  1736 
  1724     static <T> String toString(Collection<T> c,
  1737     /**
  1725                                CharSequence prefix,
  1738      * Associates a module descriptor's zip entry name along with its
  1726                                CharSequence suffix ) {
  1739      * bytes and an optional URI. Used when describing modules.
  1727         if (c.isEmpty())
  1740      */
  1728             return "";
  1741     interface ModuleInfoEntry {
  1729         return c.stream().map(e -> e.toString())
  1742        String name();
  1730                            .collect(joining(", ", prefix, suffix));
  1743        Optional<String> uriString();
  1731     }
  1744        InputStream bytes() throws IOException;
  1732 
  1745     }
       
  1746 
       
  1747     static class ZipFileModuleInfoEntry implements ModuleInfoEntry {
       
  1748         private final ZipFile zipFile;
       
  1749         private final ZipEntry entry;
       
  1750         ZipFileModuleInfoEntry(ZipFile zipFile, ZipEntry entry) {
       
  1751             this.zipFile = zipFile;
       
  1752             this.entry = entry;
       
  1753         }
       
  1754         @Override public String name() { return entry.getName(); }
       
  1755         @Override public InputStream bytes() throws IOException {
       
  1756             return zipFile.getInputStream(entry);
       
  1757         }
       
  1758         /** Returns an optional containing the effective URI. */
       
  1759         @Override public Optional<String> uriString() {
       
  1760             String uri = (Paths.get(zipFile.getName())).toUri().toString();
       
  1761             uri = "jar:" + uri + "/!" + entry.getName();
       
  1762             return Optional.of(uri);
       
  1763         }
       
  1764     }
       
  1765 
       
  1766     static class StreamedModuleInfoEntry implements ModuleInfoEntry {
       
  1767         private final String name;
       
  1768         private final byte[] bytes;
       
  1769         StreamedModuleInfoEntry(String name, byte[] bytes) {
       
  1770             this.name = name;
       
  1771             this.bytes = bytes;
       
  1772         }
       
  1773         @Override public String name() { return name; }
       
  1774         @Override public InputStream bytes() throws IOException {
       
  1775             return new ByteArrayInputStream(bytes);
       
  1776         }
       
  1777         /** Returns an empty optional. */
       
  1778         @Override public Optional<String> uriString() {
       
  1779             return Optional.empty();  // no URI can be derived
       
  1780         }
       
  1781     }
       
  1782 
       
  1783     /** Describes a module from a given zip file. */
  1733     private boolean describeModule(ZipFile zipFile) throws IOException {
  1784     private boolean describeModule(ZipFile zipFile) throws IOException {
  1734         ZipEntry[] zes = zipFile.stream()
  1785         ZipFileModuleInfoEntry[] infos = zipFile.stream()
  1735             .filter(e -> isModuleInfoEntry(e.getName()))
  1786                 .filter(e -> isModuleInfoEntry(e.getName()))
  1736             .sorted(Validator.ENTRY_COMPARATOR)
  1787                 .sorted(Validator.ENTRY_COMPARATOR)
  1737             .toArray(ZipEntry[]::new);
  1788                 .map(e -> new ZipFileModuleInfoEntry(zipFile, e))
  1738 
  1789                 .toArray(ZipFileModuleInfoEntry[]::new);
  1739         if (zes.length == 0) {
  1790 
  1740             // No module descriptor found, derive the automatic module name
  1791         if (infos.length == 0) {
       
  1792             // No module descriptor found, derive and describe the automatic module
  1741             String fn = zipFile.getName();
  1793             String fn = zipFile.getName();
  1742             ModuleFinder mf = ModuleFinder.of(Paths.get(fn));
  1794             ModuleFinder mf = ModuleFinder.of(Paths.get(fn));
  1743             try {
  1795             try {
  1744                 Set<ModuleReference> mref = mf.findAll();
  1796                 Set<ModuleReference> mref = mf.findAll();
  1745                 if (mref.isEmpty()) {
  1797                 if (mref.isEmpty()) {
  1746                     output(formatMsg("error.unable.derive.automodule", fn));
  1798                     output(formatMsg("error.unable.derive.automodule", fn));
  1747                     return true;
  1799                     return true;
  1748                 }
  1800                 }
  1749                 ModuleDescriptor md = mref.iterator().next().descriptor();
  1801                 ModuleDescriptor md = mref.iterator().next().descriptor();
  1750                 output(getMsg("out.automodule"));
  1802                 output(getMsg("out.automodule") + "\n");
  1751                 describeModule(md, null, null, "automatic");
  1803                 describeModule(md, null, null, "");
  1752             } catch (FindException e) {
  1804             } catch (FindException e) {
  1753                 String msg = formatMsg("error.unable.derive.automodule", fn);
  1805                 String msg = formatMsg("error.unable.derive.automodule", fn);
  1754                 Throwable t = e.getCause();
  1806                 Throwable t = e.getCause();
  1755                 if (t != null)
  1807                 if (t != null)
  1756                     msg = msg + "\n" + t.getMessage();
  1808                     msg = msg + "\n" + t.getMessage();
  1757                 output(msg);
  1809                 output(msg);
  1758             }
  1810             }
  1759         } else {
  1811         } else {
  1760             for (ZipEntry ze : zes) {
  1812             return describeModuleFromEntries(infos);
  1761                 try (InputStream is = zipFile.getInputStream(ze)) {
       
  1762                     describeModule(is, ze.getName());
       
  1763                 }
       
  1764             }
       
  1765         }
  1813         }
  1766         return true;
  1814         return true;
  1767     }
  1815     }
  1768 
  1816 
  1769     private boolean describeModule(FileInputStream fis)
  1817     private boolean describeModuleFromStream(FileInputStream fis)
  1770         throws IOException
  1818         throws IOException
  1771     {
  1819     {
       
  1820         List<ModuleInfoEntry> infos = new LinkedList<>();
       
  1821 
  1772         try (BufferedInputStream bis = new BufferedInputStream(fis);
  1822         try (BufferedInputStream bis = new BufferedInputStream(fis);
  1773              ZipInputStream zis = new ZipInputStream(bis)) {
  1823              ZipInputStream zis = new ZipInputStream(bis)) {
  1774             ZipEntry e;
  1824             ZipEntry e;
  1775             while ((e = zis.getNextEntry()) != null) {
  1825             while ((e = zis.getNextEntry()) != null) {
  1776                 String ename = e.getName();
  1826                 String ename = e.getName();
  1777                 if (isModuleInfoEntry(ename)){
  1827                 if (isModuleInfoEntry(ename)) {
  1778                     moduleInfos.put(ename, zis.readAllBytes());
  1828                     infos.add(new StreamedModuleInfoEntry(ename, zis.readAllBytes()));
  1779                 }
  1829                 }
  1780             }
  1830             }
  1781         }
  1831         }
  1782         if (moduleInfos.size() == 0)
  1832 
       
  1833         if (infos.size() == 0)
  1783             return false;
  1834             return false;
  1784         String[] names = moduleInfos.keySet().stream()
  1835 
  1785             .sorted(Validator.ENTRYNAME_COMPARATOR)
  1836         ModuleInfoEntry[] sorted = infos.stream()
  1786             .toArray(String[]::new);
  1837                 .sorted(Comparator.comparing(ModuleInfoEntry::name, ENTRYNAME_COMPARATOR))
  1787         for (String name : names) {
  1838                 .toArray(ModuleInfoEntry[]::new);
  1788             describeModule(new ByteArrayInputStream(moduleInfos.get(name)), name);
  1839 
       
  1840         return describeModuleFromEntries(sorted);
       
  1841     }
       
  1842 
       
  1843     private boolean lessThanEqualReleaseValue(ModuleInfoEntry entry) {
       
  1844         return intVersionFromEntry(entry) <= releaseValue ? true : false;
       
  1845     }
       
  1846 
       
  1847     private static String versionFromEntryName(String name) {
       
  1848         String s = name.substring(VERSIONS_DIR_LENGTH);
       
  1849         return s.substring(0, s.indexOf("/"));
       
  1850     }
       
  1851 
       
  1852     private static int intVersionFromEntry(ModuleInfoEntry entry) {
       
  1853         String name = entry.name();
       
  1854         if (!name.startsWith(VERSIONS_DIR))
       
  1855             return BASE_VERSION;
       
  1856 
       
  1857         String s = name.substring(VERSIONS_DIR_LENGTH);
       
  1858         s = s.substring(0, s.indexOf('/'));
       
  1859         return Integer.valueOf(s);
       
  1860     }
       
  1861 
       
  1862     /**
       
  1863      * Describes a single module descriptor, determined by the specified
       
  1864      * --release, if any, from the given ordered entries.
       
  1865      * The given infos must be ordered as per ENTRY_COMPARATOR.
       
  1866      */
       
  1867     private boolean describeModuleFromEntries(ModuleInfoEntry[] infos)
       
  1868         throws IOException
       
  1869     {
       
  1870         assert infos.length > 0;
       
  1871 
       
  1872         // Informative: output all non-root descriptors, if any
       
  1873         String releases = Arrays.stream(infos)
       
  1874                 .filter(e -> !e.name().equals(MODULE_INFO))
       
  1875                 .map(ModuleInfoEntry::name)
       
  1876                 .map(Main::versionFromEntryName)
       
  1877                 .collect(joining(" "));
       
  1878         if (!releases.equals(""))
       
  1879             output("releases: " + releases + "\n");
       
  1880 
       
  1881         // Describe the operative descriptor for the specified --release, if any
       
  1882         if (releaseValue != -1) {
       
  1883             ModuleInfoEntry entry = null;
       
  1884             int i = 0;
       
  1885             while (i < infos.length && lessThanEqualReleaseValue(infos[i])) {
       
  1886                 entry = infos[i];
       
  1887                 i++;
       
  1888             }
       
  1889 
       
  1890             if (entry == null) {
       
  1891                 output(formatMsg("error.no.operative.descriptor",
       
  1892                                  String.valueOf(releaseValue)));
       
  1893                 return false;
       
  1894             }
       
  1895 
       
  1896             String uriString = entry.uriString().orElse("");
       
  1897             try (InputStream is = entry.bytes()) {
       
  1898                 describeModule(is, uriString);
       
  1899             }
       
  1900         } else {
       
  1901             // no specific --release specified, output the root, if any
       
  1902             if (infos[0].name().equals(MODULE_INFO)) {
       
  1903                 String uriString = infos[0].uriString().orElse("");
       
  1904                 try (InputStream is = infos[0].bytes()) {
       
  1905                     describeModule(is, uriString);
       
  1906                 }
       
  1907             } else {
       
  1908                 // no root, output message to specify --release
       
  1909                 output(getMsg("error.no.root.descriptor"));
       
  1910             }
  1789         }
  1911         }
  1790         return true;
  1912         return true;
  1791     }
  1913     }
  1792 
  1914 
  1793     static <T> String toString(Collection<T> set) {
  1915     static <T> String toString(Collection<T> set) {
  1794         if (set.isEmpty()) { return ""; }
  1916         if (set.isEmpty()) { return ""; }
  1795         return set.stream().map(e -> e.toString().toLowerCase(Locale.ROOT))
  1917         return " " + set.stream().map(e -> e.toString().toLowerCase(Locale.ROOT))
  1796                   .collect(joining(" "));
  1918                   .sorted().collect(joining(" "));
  1797     }
  1919     }
  1798 
  1920 
  1799     private void describeModule(InputStream entryInputStream, String ename)
  1921 
       
  1922     private void describeModule(InputStream entryInputStream, String uriString)
  1800         throws IOException
  1923         throws IOException
  1801     {
  1924     {
  1802         ModuleInfo.Attributes attrs = ModuleInfo.read(entryInputStream, null);
  1925         ModuleInfo.Attributes attrs = ModuleInfo.read(entryInputStream, null);
  1803         ModuleDescriptor md = attrs.descriptor();
  1926         ModuleDescriptor md = attrs.descriptor();
  1804         ModuleTarget target = attrs.target();
  1927         ModuleTarget target = attrs.target();
  1805         ModuleHashes hashes = attrs.recordedHashes();
  1928         ModuleHashes hashes = attrs.recordedHashes();
  1806 
  1929 
  1807         describeModule(md, target, hashes, ename);
  1930         describeModule(md, target, hashes, uriString);
  1808     }
  1931     }
  1809 
  1932 
  1810     private void describeModule(ModuleDescriptor md,
  1933     private void describeModule(ModuleDescriptor md,
  1811                                 ModuleTarget target,
  1934                                 ModuleTarget target,
  1812                                 ModuleHashes hashes,
  1935                                 ModuleHashes hashes,
  1813                                 String ename)
  1936                                 String uriString)
  1814         throws IOException
  1937         throws IOException
  1815     {
  1938     {
  1816         StringBuilder sb = new StringBuilder();
  1939         StringBuilder sb = new StringBuilder();
  1817         sb.append("\nmodule ")
  1940 
  1818           .append(md.toNameAndVersion())
  1941         sb.append(md.toNameAndVersion());
  1819           .append(" (").append(ename).append(")");
  1942 
  1820 
  1943         if (!uriString.equals(""))
       
  1944             sb.append(" ").append(uriString);
  1821         if (md.isOpen())
  1945         if (md.isOpen())
  1822             sb.append("\n  open ");
  1946             sb.append(" open");
  1823 
  1947         if (md.isAutomatic())
  1824         md.requires().stream()
  1948             sb.append(" automatic");
  1825             .sorted(Comparator.comparing(Requires::name))
  1949         sb.append("\n");
  1826             .forEach(r -> {
  1950 
  1827                 sb.append("\n  requires ");
  1951         // unqualified exports (sorted by package)
  1828                 if (!r.modifiers().isEmpty())
  1952         md.exports().stream()
  1829                     sb.append(toString(r.modifiers())).append(" ");
  1953                 .sorted(Comparator.comparing(Exports::source))
  1830                 sb.append(r.name());
  1954                 .filter(e -> !e.isQualified())
  1831             });
  1955                 .forEach(e -> sb.append("exports ").append(e.source())
  1832 
  1956                                 .append(toString(e.modifiers())).append("\n"));
       
  1957 
       
  1958         // dependences
       
  1959         md.requires().stream().sorted()
       
  1960                 .forEach(r -> sb.append("requires ").append(r.name())
       
  1961                                 .append(toString(r.modifiers())).append("\n"));
       
  1962 
       
  1963         // service use and provides
  1833         md.uses().stream().sorted()
  1964         md.uses().stream().sorted()
  1834             .forEach(p -> sb.append("\n  uses ").append(p));
  1965                 .forEach(s -> sb.append("uses ").append(s).append("\n"));
  1835 
  1966 
       
  1967         md.provides().stream()
       
  1968                 .sorted(Comparator.comparing(Provides::service))
       
  1969                 .forEach(p -> sb.append("provides ").append(p.service())
       
  1970                                 .append(" with")
       
  1971                                 .append(toString(p.providers()))
       
  1972                                 .append("\n"));
       
  1973 
       
  1974         // qualified exports
  1836         md.exports().stream()
  1975         md.exports().stream()
  1837             .sorted(Comparator.comparing(Exports::source))
  1976                 .sorted(Comparator.comparing(Exports::source))
  1838             .forEach(p -> sb.append("\n  exports ").append(p));
  1977                 .filter(Exports::isQualified)
  1839 
  1978                 .forEach(e -> sb.append("qualified exports ").append(e.source())
       
  1979                                 .append(" to").append(toString(e.targets()))
       
  1980                                 .append("\n"));
       
  1981 
       
  1982         // open packages
  1840         md.opens().stream()
  1983         md.opens().stream()
  1841             .sorted(Comparator.comparing(Opens::source))
  1984                 .sorted(Comparator.comparing(Opens::source))
  1842             .forEach(p -> sb.append("\n  opens ").append(p));
  1985                 .filter(o -> !o.isQualified())
  1843 
  1986                 .forEach(o -> sb.append("opens ").append(o.source())
  1844         Set<String> concealed = new HashSet<>(md.packages());
  1987                                  .append(toString(o.modifiers()))
       
  1988                                  .append("\n"));
       
  1989 
       
  1990         md.opens().stream()
       
  1991                 .sorted(Comparator.comparing(Opens::source))
       
  1992                 .filter(Opens::isQualified)
       
  1993                 .forEach(o -> sb.append("qualified opens ").append(o.source())
       
  1994                                  .append(toString(o.modifiers()))
       
  1995                                  .append(" to").append(toString(o.targets()))
       
  1996                                  .append("\n"));
       
  1997 
       
  1998         // non-exported/non-open packages
       
  1999         Set<String> concealed = new TreeSet<>(md.packages());
  1845         md.exports().stream().map(Exports::source).forEach(concealed::remove);
  2000         md.exports().stream().map(Exports::source).forEach(concealed::remove);
  1846         md.opens().stream().map(Opens::source).forEach(concealed::remove);
  2001         md.opens().stream().map(Opens::source).forEach(concealed::remove);
  1847         concealed.stream().sorted()
  2002         concealed.forEach(p -> sb.append("contains ").append(p).append("\n"));
  1848             .forEach(p -> sb.append("\n  contains ").append(p));
  2003 
  1849 
  2004         md.mainClass().ifPresent(v -> sb.append("main-class ").append(v).append("\n"));
  1850         md.provides().stream()
       
  1851             .sorted(Comparator.comparing(Provides::service))
       
  1852             .forEach(p -> sb.append("\n  provides ").append(p.service())
       
  1853                             .append(" with ")
       
  1854                             .append(toString(p.providers())));
       
  1855 
       
  1856         md.mainClass().ifPresent(v -> sb.append("\n  main-class " + v));
       
  1857 
  2005 
  1858         if (target != null) {
  2006         if (target != null) {
  1859             String osName = target.osName();
  2007             String targetPlatform = target.targetPlatform();
  1860             if (osName != null)
  2008             if (!targetPlatform.isEmpty())
  1861                 sb.append("\n  operating-system-name " + osName);
  2009                 sb.append("platform ").append(targetPlatform).append("\n");
  1862             String osArch = target.osArch();
       
  1863             if (osArch != null)
       
  1864                 sb.append("\n  operating-system-architecture " + osArch);
       
  1865        }
  2010        }
  1866 
  2011 
  1867        if (hashes != null) {
  2012        if (hashes != null) {
  1868            hashes.names().stream().sorted().forEach(
  2013            hashes.names().stream().sorted().forEach(
  1869                    mod -> sb.append("\n  hashes ").append(mod).append(" ")
  2014                    mod -> sb.append("hashes ").append(mod).append(" ")
  1870                             .append(hashes.algorithm()).append(" ")
  2015                             .append(hashes.algorithm()).append(" ")
  1871                             .append(toHex(hashes.hashFor(mod))));
  2016                             .append(toHex(hashes.hashFor(mod)))
       
  2017                             .append("\n"));
  1872         }
  2018         }
  1873 
  2019 
  1874         output(sb.toString());
  2020         output(sb.toString());
  1875     }
  2021     }
  1876 
  2022