src/jdk.jartool/share/classes/sun/tools/jar/Validator.java

Print this page




  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 sun.tools.jar;
  27 
  28 import java.io.File;
  29 import java.io.IOException;
  30 import java.io.InputStream;
  31 import java.lang.module.InvalidModuleDescriptorException;
  32 import java.lang.module.ModuleDescriptor;
  33 import java.lang.module.ModuleDescriptor.Exports;
  34 import java.lang.module.InvalidModuleDescriptorException;
  35 import java.lang.module.ModuleDescriptor.Opens;
  36 import java.lang.module.ModuleDescriptor.Provides;
  37 import java.lang.module.ModuleDescriptor.Requires;
  38 import java.util.Collections;
  39 import java.util.Comparator;
  40 import java.util.HashMap;
  41 import java.util.HashSet;
  42 import java.util.List;
  43 import java.util.Map;
  44 import java.util.Set;
  45 import java.util.function.Consumer;
  46 import java.util.jar.JarEntry;
  47 import java.util.jar.JarFile;
  48 import java.util.zip.ZipEntry;

  49 
  50 import static java.util.jar.JarFile.MANIFEST_NAME;
  51 import static sun.tools.jar.Main.VERSIONS_DIR;
  52 import static sun.tools.jar.Main.VERSIONS_DIR_LENGTH;
  53 import static sun.tools.jar.Main.MODULE_INFO;
  54 import static sun.tools.jar.Main.getMsg;
  55 import static sun.tools.jar.Main.formatMsg;
  56 import static sun.tools.jar.Main.formatMsg2;
  57 import static sun.tools.jar.Main.toBinaryName;
  58 import static sun.tools.jar.Main.isModuleInfoEntry;
  59 
  60 final class Validator {
  61     private final static boolean DEBUG = Boolean.getBoolean("jar.debug");
  62     private final  Map<String,FingerPrint> fps = new HashMap<>();
  63     private final Main main;
  64     private final JarFile jf;
  65     private int oldVersion = -1;
  66     private String currentTopLevelName;
  67     private boolean isValid = true;
  68     private Set<String> concealedPkgs = Collections.emptySet();
  69     private ModuleDescriptor md;
  70     private String mdName;
  71 
  72     private Validator(Main main, JarFile jf) {
  73         this.main = main;
  74         this.jf = jf;
  75         checkModuleDescriptor(MODULE_INFO);
  76     }
  77 
  78     static boolean validate(Main main, JarFile jf) throws IOException {
  79         return new Validator(main, jf).validate();
  80     }
  81 
  82     private boolean validate() {
  83         try {
  84             jf.stream()
  85               .filter(e -> !e.isDirectory() &&
  86                       !e.getName().equals(MANIFEST_NAME))
  87               .sorted(ENTRY_COMPARATOR)
  88               .forEachOrdered(e -> validate(e));
  89             return isValid;










  90         } catch (InvalidJarException e) {
  91             error(formatMsg("error.validator.bad.entry.name", e.getMessage()));
  92         }
  93         return false;
  94     }
  95 
  96     private static class InvalidJarException extends RuntimeException {
  97         private static final long serialVersionUID = -3642329147299217726L;
  98         InvalidJarException(String msg) {
  99             super(msg);
 100         }
 101     }
 102 
 103     // sort base entries before versioned entries, and sort entry classes with
 104     // nested classes so that the top level class appears before the associated
 105     // nested class
 106     static Comparator<String> ENTRYNAME_COMPARATOR = (s1, s2) ->  {
 107 
 108         if (s1.equals(s2)) return 0;
 109         boolean b1 = s1.startsWith(VERSIONS_DIR);
 110         boolean b2 = s2.startsWith(VERSIONS_DIR);
 111         if (b1 && !b2) return 1;
 112         if (!b1 && b2) return -1;
 113         int n = 0; // starting char for String compare
 114         if (b1 && b2) {
 115             // normally strings would be sorted so "10" goes before "9", but
 116             // version number strings need to be sorted numerically
 117             n = VERSIONS_DIR.length();   // skip the common prefix
 118             int i1 = s1.indexOf('/', n);
 119             int i2 = s2.indexOf('/', n);
 120             if (i1 == -1) throw new InvalidJarException(s1);
 121             if (i2 == -1) throw new InvalidJarException(s2);
 122             // shorter version numbers go first
 123             if (i1 != i2) return i1 - i2;
 124             // otherwise, handle equal length numbers below
 125         }
 126         int l1 = s1.length();
 127         int l2 = s2.length();
 128         int lim = Math.min(l1, l2);
 129         for (int k = n; k < lim; k++) {
 130             char c1 = s1.charAt(k);
 131             char c2 = s2.charAt(k);
 132             if (c1 != c2) {
 133                 // change natural ordering so '.' comes before '$'
 134                 // i.e. top level classes come before nested classes
 135                 if (c1 == '$' && c2 == '.') return 1;
 136                 if (c1 == '.' && c2 == '$') return -1;
 137                 return c1 - c2;
 138             }
 139         }
 140         return l1 - l2;
 141     };
 142 
 143     static Comparator<ZipEntry> ENTRY_COMPARATOR =
 144         Comparator.comparing(ZipEntry::getName, ENTRYNAME_COMPARATOR);



 145 
 146     /*
 147      *  Validator has state and assumes entries provided to accept are ordered
 148      *  from base entries first and then through the versioned entries in
 149      *  ascending version order.  Also, to find isolated nested classes,
 150      *  classes must be ordered so that the top level class is before the associated
 151      *  nested class(es).
 152     */
 153     public void validate(JarEntry je) {
 154         String entryName = je.getName();
 155 
 156         // directories are always accepted
 157         if (entryName.endsWith("/")) {
 158             debug("%s is a directory", entryName);
 159             return;
 160         }
 161 
 162         // validate the versioned module-info
 163         if (isModuleInfoEntry(entryName)) {
 164             if (!entryName.equals(mdName))
 165                 checkModuleDescriptor(entryName);
 166             return;
 167         }
 168 
 169         // figure out the version and basename from the JarEntry
 170         int version;
 171         String basename;
 172         String versionStr = null;;
 173         if (entryName.startsWith(VERSIONS_DIR)) {
 174             int n = entryName.indexOf("/", VERSIONS_DIR_LENGTH);
 175             if (n == -1) {
 176                 error(formatMsg("error.validator.version.notnumber", entryName));
 177                 isValid = false;
 178                 return;
 179             }
 180             versionStr = entryName.substring(VERSIONS_DIR_LENGTH, n);
 181             try {
 182                 version = Integer.parseInt(versionStr);
 183             } catch (NumberFormatException x) {
 184                 error(formatMsg("error.validator.version.notnumber", entryName));
 185                 isValid = false;
 186                 return;
 187             }
 188             if (n == entryName.length()) {
 189                 error(formatMsg("error.validator.entryname.tooshort", entryName));
 190                 isValid = false;
 191                 return;
 192             }
 193             basename = entryName.substring(n + 1);
 194         } else {
 195             version = 0;
 196             basename = entryName;
 197         }
 198         debug("\n===================\nversion %d %s", version, entryName);
 199 
 200         if (oldVersion != version) {
 201             oldVersion = version;
 202             currentTopLevelName = null;
 203             if (md == null && versionStr != null) {
 204                 // don't have a base module-info.class yet, try to see if
 205                 // a versioned one exists
 206                 checkModuleDescriptor(VERSIONS_DIR + versionStr + "/" + MODULE_INFO);
 207             }

 208         }
 209 
 210         // analyze the entry, keeping key attributes
 211         FingerPrint fp;
 212         try (InputStream is = jf.getInputStream(je)) {
 213             fp = new FingerPrint(basename, is.readAllBytes());
 214         } catch (IOException x) {
 215             error(x.getMessage());
 216             isValid = false;
 217             return;
 218         }
 219         String internalName = fp.name();
 220 
 221         // process a base entry paying attention to nested classes
 222         if (version == 0) {
 223             debug("base entry found");
 224             if (fp.isNestedClass()) {
 225                 debug("nested class found");
 226                 if (fp.topLevelName().equals(currentTopLevelName)) {
 227                     fps.put(internalName, fp);
 228                     return;
 229                 }
 230                 error(formatMsg("error.validator.isolated.nested.class", entryName));







 231                 isValid = false;
 232                 return;
 233             }
 234             // top level class or resource entry
 235             if (fp.isClass()) {
 236                 currentTopLevelName = fp.topLevelName();
 237                 if (!checkInternalName(entryName, basename, internalName)) {
 238                     isValid = false;
 239                     return;
 240                 }
 241             }
 242             fps.put(internalName, fp);
 243             return;
 244         }
 245 









 246         // process a versioned entry, look for previous entry with same name
 247         FingerPrint matchFp = fps.get(internalName);
 248         debug("looking for match");
 249         if (matchFp == null) {
 250             debug("no match found");
 251             if (fp.isClass()) {
 252                 if (fp.isNestedClass()) {
 253                     if (!checkNestedClass(version, entryName, internalName, fp)) {
 254                         isValid = false;
 255                     }
 256                     return;
 257                 }
 258                 if (fp.isPublicClass()) {
 259                     if (!isConcealed(internalName)) {
 260                         error(Main.formatMsg("error.validator.new.public.class", entryName));
 261                         isValid = false;
 262                         return;
 263                     }
 264                     warn(formatMsg("warn.validator.concealed.public.class", entryName));
 265                     debug("%s is a public class entry in a concealed package", entryName);

 266                 }
 267                 debug("%s is a non-public class entry", entryName);
 268                 fps.put(internalName, fp);
 269                 currentTopLevelName = fp.topLevelName();
 270                 return;
 271             }
 272             debug("%s is a resource entry");
 273             fps.put(internalName, fp);
 274             return;
 275         }
 276         debug("match found");
 277 
 278         // are the two classes/resources identical?
 279         if (fp.isIdentical(matchFp)) {
 280             warn(formatMsg("warn.validator.identical.entry", entryName));
 281             return;  // it's okay, just takes up room
 282         }
 283         debug("sha1 not equal -- different bytes");
 284 
 285         // ok, not identical, check for compatible class version and api
 286         if (fp.isClass()) {
 287             if (fp.isNestedClass()) {
 288                 if (!checkNestedClass(version, entryName, internalName, fp)) {
 289                     isValid = false;
 290                 }
 291                 return;
 292             }
 293             debug("%s is a class entry", entryName);
 294             if (!fp.isCompatibleVersion(matchFp)) {
 295                 error(formatMsg("error.validator.incompatible.class.version", entryName));
 296                 isValid = false;
 297                 return;
 298             }
 299             if (!fp.isSameAPI(matchFp)) {
 300                 error(formatMsg("error.validator.different.api", entryName));
 301                 isValid = false;
 302                 return;
 303             }
 304             if (!checkInternalName(entryName, basename, internalName)) {
 305                 isValid = false;
 306                 return;
 307             }
 308             debug("fingerprints same -- same api");
 309             fps.put(internalName, fp);
 310             currentTopLevelName = fp.topLevelName();
 311             return;
 312         }
 313         debug("%s is a resource", entryName);
 314 
 315         warn(formatMsg("warn.validator.resources.with.same.name", entryName));
 316         fps.put(internalName, fp);
 317         return;

 318     }
 319 
 320     /**
 321      * Checks whether or not the given versioned module descriptor's attributes
 322      * are valid when compared against the root/base module descriptor.
 323      *
 324      * A versioned module descriptor must be identical to the root/base module
 325      * descriptor, with two exceptions:
 326      *  - A versioned descriptor can have different non-public `requires`
 327      *    clauses of platform ( `java.*` and `jdk.*` ) modules, and
 328      *  - A versioned descriptor can have different `uses` clauses, even of
 329      *    service types defined outside of the platform modules.
 330      */
 331     private void checkModuleDescriptor(String miName) {
 332         ZipEntry je = jf.getEntry(miName);
 333         if (je != null) {
 334             try (InputStream jis = jf.getInputStream(je)) {
 335                 ModuleDescriptor md = ModuleDescriptor.read(jis);
 336                 // Initialize the base md if it's not yet. A "base" md can be either the
 337                 // root module-info.class or the first versioned module-info.class
 338                 ModuleDescriptor base = this.md;
 339 
 340                 if (base == null) {
 341                     concealedPkgs = new HashSet<>(md.packages());
 342                     md.exports().stream().map(Exports::source).forEach(concealedPkgs::remove);
 343                     md.opens().stream().map(Opens::source).forEach(concealedPkgs::remove);
 344                     // must have the implementation class of the services it 'provides'.
 345                     if (md.provides().stream().map(Provides::providers)
 346                           .flatMap(List::stream)
 347                           .filter(p -> jf.getEntry(toBinaryName(p)) == null)
 348                           .peek(p -> error(formatMsg("error.missing.provider", p)))
 349                           .count() != 0) {
 350                         isValid = false;
 351                         return;
 352                     }
 353                     this.md = md;
 354                     this.mdName = miName;
 355                     return;
 356                 }
 357 
 358                 if (!base.name().equals(md.name())) {
 359                     error(getMsg("error.validator.info.name.notequal"));
 360                     isValid = false;
 361                 }
 362                 if (!base.requires().equals(md.requires())) {
 363                     Set<Requires> baseRequires = base.requires();
 364                     for (Requires r : md.requires()) {
 365                         if (baseRequires.contains(r))
 366                             continue;
 367                         if (r.modifiers().contains(Requires.Modifier.TRANSITIVE)) {
 368                             error(getMsg("error.validator.info.requires.transitive"));
 369                             isValid = false;
 370                         } else if (!isPlatformModule(r.name())) {
 371                             error(getMsg("error.validator.info.requires.added"));
 372                             isValid = false;
 373                         }
 374                     }
 375                     for (Requires r : baseRequires) {
 376                         Set<Requires> mdRequires = md.requires();
 377                         if (mdRequires.contains(r))
 378                             continue;
 379                         if (!isPlatformModule(r.name())) {
 380                             error(getMsg("error.validator.info.requires.dropped"));
 381                             isValid = false;
 382                         }
 383                     }
 384                 }
 385                 if (!base.exports().equals(md.exports())) {
 386                     error(getMsg("error.validator.info.exports.notequal"));
 387                     isValid = false;
 388                 }
 389                 if (!base.opens().equals(md.opens())) {
 390                     error(getMsg("error.validator.info.opens.notequal"));
 391                     isValid = false;
 392                 }
 393                 if (!base.provides().equals(md.provides())) {
 394                     error(getMsg("error.validator.info.provides.notequal"));
 395                     isValid = false;
 396                 }
 397                 if (!base.mainClass().equals(md.mainClass())) {
 398                     error(formatMsg("error.validator.info.manclass.notequal", je.getName()));
 399                     isValid = false;
 400                 }
 401                 if (!base.version().equals(md.version())) {
 402                     error(formatMsg("error.validator.info.version.notequal", je.getName()));
 403                     isValid = false;
 404                 }
 405             } catch (Exception x) {
 406                 error(x.getMessage() + " : " + miName);
 407                 this.isValid = false;
 408             }
 409         }
 410     }
 411 
 412     private static boolean isPlatformModule(String name) {
 413         return name.startsWith("java.") || name.startsWith("jdk.");
 414     }
 415 
 416     private boolean checkInternalName(String entryName, String basename, String internalName) {
 417         String className = className(basename);
 418         if (internalName.equals(className)) {
 419             return true;
 420         }
 421         error(formatMsg2("error.validator.names.mismatch",
 422                 entryName, internalName.replace("/", ".")));
 423         return false;
 424     }
 425 
 426     private boolean checkNestedClass(int version, String entryName, String internalName, FingerPrint fp) {
 427         debug("%s is a nested class entry in top level class %s", entryName, fp.topLevelName());
 428         if (fp.topLevelName().equals(currentTopLevelName)) {
 429             debug("%s (top level class) was accepted", fp.topLevelName());
 430             fps.put(internalName, fp);
 431             return true;
 432         }
 433         debug("top level class was not accepted");
 434         error(formatMsg("error.validator.isolated.nested.class", entryName));
 435         return false;
 436     }
 437 
 438     private String className(String entryName) {
 439         return entryName.endsWith(".class") ? entryName.substring(0, entryName.length() - 6) : null;
 440     }
 441 
 442     private boolean isConcealed(String internalName) {
 443         if (concealedPkgs.isEmpty()) {
 444             return false;
 445         }
 446         int idx = internalName.lastIndexOf('/');
 447         String pkgName = idx != -1 ? internalName.substring(0, idx).replace('/', '.') : "";
 448         return concealedPkgs.contains(pkgName);
 449     }
 450 
 451     private void debug(String fmt, Object... args) {
 452         if (DEBUG) System.err.format(fmt, args);




 453     }
 454 
 455     private void error(String msg) {
 456         main.error(msg);
 457     }
 458 





 459     private void warn(String msg) {
 460         main.warn(msg);
 461     }
 462 
 463 }


  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 sun.tools.jar;
  27 
  28 import java.io.File;
  29 import java.io.IOException;
  30 import java.io.InputStream;

  31 import java.lang.module.ModuleDescriptor;
  32 import java.lang.module.ModuleDescriptor.Exports;

  33 import java.lang.module.ModuleDescriptor.Opens;
  34 import java.lang.module.ModuleDescriptor.Provides;
  35 import java.lang.module.ModuleDescriptor.Requires;
  36 import java.util.Collections;

  37 import java.util.HashMap;
  38 import java.util.HashSet;
  39 import java.util.List;
  40 import java.util.Map;
  41 import java.util.Set;
  42 import java.util.TreeMap;
  43 import java.util.function.Function;
  44 import java.util.stream.Collectors;
  45 import java.util.zip.ZipEntry;
  46 import java.util.zip.ZipFile;
  47 
  48 import static java.util.jar.JarFile.MANIFEST_NAME;
  49 import static sun.tools.jar.Main.VERSIONS_DIR;
  50 import static sun.tools.jar.Main.VERSIONS_DIR_LENGTH;
  51 import static sun.tools.jar.Main.MODULE_INFO;
  52 import static sun.tools.jar.Main.getMsg;
  53 import static sun.tools.jar.Main.formatMsg;
  54 import static sun.tools.jar.Main.formatMsg2;
  55 import static sun.tools.jar.Main.toBinaryName;

  56 
  57 final class Validator {
  58 
  59     private final Map<String,FingerPrint> classes = new HashMap<>();
  60     private final Main main;
  61     private final ZipFile zf;


  62     private boolean isValid = true;
  63     private Set<String> concealedPkgs = Collections.emptySet();
  64     private ModuleDescriptor md;
  65     private String mdName;
  66 
  67     private Validator(Main main, ZipFile zf) {
  68         this.main = main;
  69         this.zf = zf;
  70         checkModuleDescriptor(MODULE_INFO);
  71     }
  72 
  73     static boolean validate(Main main, ZipFile zf) throws IOException {
  74         return new Validator(main, zf).validate();
  75     }
  76 
  77     private boolean validate() {
  78         try {
  79             zf.stream()
  80               .filter(e -> e.getName().endsWith(".class"))
  81               .map(this::getFingerPrint)
  82               .filter(FingerPrint::isClass)    // skip any non-class entry
  83               .collect(Collectors.groupingBy(
  84                       FingerPrint::mrversion,
  85                       TreeMap::new,
  86                       Collectors.toMap(FingerPrint::className,
  87                                        Function.identity(),
  88                                        this::sameNameFingerPrint)))
  89               .forEach((version, entries) -> {
  90                       if (version == 0)
  91                           validateBase(entries);
  92                       else
  93                           validateVersioned(entries);
  94                   });
  95         } catch (InvalidJarException e) {
  96             errorAndInvalid(e.getMessage());
  97         }
  98         return isValid;
  99     }
 100 
 101     static class InvalidJarException extends RuntimeException {
 102         private static final long serialVersionUID = -3642329147299217726L;
 103         InvalidJarException(String msg) {
 104             super(msg);
 105         }
 106     }
 107 
 108     private FingerPrint sameNameFingerPrint(FingerPrint fp1, FingerPrint fp2) {
 109         checkClassName(fp1);
 110         checkClassName(fp2);
 111         // entries/classes with same name, return fp2 for now ?
 112         return fp2;































 113     }


 114 
 115     private FingerPrint getFingerPrint(ZipEntry ze) {
 116         // figure out the version and basename from the ZipEntry
 117         String ename = ze.getName();
 118         String bname = ename;
 119         int version = 0;
 120 
 121         if (ename.startsWith(VERSIONS_DIR)) {
 122             int n = ename.indexOf("/", VERSIONS_DIR_LENGTH);



























 123             if (n == -1) {
 124                 throw new InvalidJarException(
 125                     formatMsg("error.validator.version.notnumber", ename));

 126             }

 127             try {
 128                 version = Integer.parseInt(ename, VERSIONS_DIR_LENGTH, n, 10);
 129             } catch (NumberFormatException x) {
 130                 throw new InvalidJarException(
 131                     formatMsg("error.validator.version.notnumber", ename));

 132             }
 133             if (n == ename.length()) {
 134                 throw new InvalidJarException(
 135                     formatMsg("error.validator.entryname.tooshort", ename));
















 136             }
 137             bname = ename.substring(n + 1);
 138         }
 139 
 140         // return the cooresponding fingerprint entry
 141         try (InputStream is = zf.getInputStream(ze)) {
 142             return new FingerPrint(bname, ename, version, is.readAllBytes());

 143         } catch (IOException x) {
 144            throw new InvalidJarException(x.getMessage());


 145         }










 146     }
 147 
 148     /*
 149      *  Validates (a) if there is any isolated nested class, and (b) if the
 150      *  class name in class file (by asm) matches the entry's basename.
 151      */ 
 152     public void validateBase(Map<String, FingerPrint> fps) {
 153         fps.values().forEach( fp -> {        
 154             if (!checkClassName(fp)) {
 155                 isValid = false;
 156                 return;
 157             }
 158             if (fp.isNestedClass()) {
 159                 if (!checkNestedClass(fp, fps)) {


 160                     isValid = false;

 161                 }
 162             }
 163             classes.put(fp.className(), fp);
 164         });
 165     }
 166 
 167     public void validateVersioned(Map<String, FingerPrint> fps) {
 168 
 169         fps.values().forEach( fp -> {
 170 
 171             // validate the versioned module-info
 172             if (MODULE_INFO.equals(fp.basename())) {
 173                 checkModuleDescriptor(fp.entryName());
 174                 return;
 175             }
 176             // process a versioned entry, look for previous entry with same name
 177             FingerPrint matchFp = classes.get(fp.className());

 178             if (matchFp == null) {
 179                 // no match found

 180                 if (fp.isNestedClass()) {
 181                     if (!checkNestedClass(fp, fps)) {
 182                         isValid = false;
 183                     }
 184                     return;
 185                 }
 186                 if (fp.isPublicClass()) {
 187                     if (!isConcealed(fp.className())) {
 188                         errorAndInvalid(formatMsg("error.validator.new.public.class",
 189                                                   fp.entryName()));
 190                         return;
 191                      }
 192                      // entry is a public class entry in a concealed package
 193                      warn(formatMsg("warn.validator.concealed.public.class",
 194                                    fp.entryName()));
 195                 }
 196                 classes.put(fp.className(), fp);


 197                 return;
 198             }





 199 
 200             // are the two classes/resources identical?
 201             if (fp.isIdentical(matchFp)) {
 202                 warn(formatMsg("warn.validator.identical.entry", fp.entryName()));
 203                 return;    // it's okay, just takes up room
 204             }

 205 
 206             // ok, not identical, check for compatible class version and api

 207             if (fp.isNestedClass()) {
 208                 if (!checkNestedClass(fp, fps)) {
 209                     isValid = false;
 210                 }
 211                 return;    // fall through, need check nested public class??
 212             }

 213             if (!fp.isCompatibleVersion(matchFp)) {
 214                 errorAndInvalid(formatMsg("error.validator.incompatible.class.version",
 215                                           fp.entryName()));
 216                 return;
 217             }
 218             if (!fp.isSameAPI(matchFp)) {
 219                 errorAndInvalid(formatMsg("error.validator.different.api",
 220                                           fp.entryName()));
 221                 return;
 222             }
 223             if (!checkClassName(fp)) {
 224                 isValid = false;
 225                 return;
 226             }
 227             classes.put(fp.className(), fp);





 228 


 229             return;
 230         });
 231     }
 232 
 233     /*
 234      * Checks whether or not the given versioned module descriptor's attributes
 235      * are valid when compared against the root/base module descriptor.
 236      *
 237      * A versioned module descriptor must be identical to the root/base module
 238      * descriptor, with two exceptions:
 239      *  - A versioned descriptor can have different non-public `requires`
 240      *    clauses of platform ( `java.*` and `jdk.*` ) modules, and
 241      *  - A versioned descriptor can have different `uses` clauses, even of
 242     *    service types defined outside of the platform modules.
 243      */
 244     private void checkModuleDescriptor(String miName) {
 245         ZipEntry ze = zf.getEntry(miName);
 246         if (ze != null) {
 247             try (InputStream jis = zf.getInputStream(ze)) {
 248                 ModuleDescriptor md = ModuleDescriptor.read(jis);
 249                 // Initialize the base md if it's not yet. A "base" md can be either the
 250                 // root module-info.class or the first versioned module-info.class
 251                 ModuleDescriptor base = this.md;
 252 
 253                 if (base == null) {
 254                     concealedPkgs = new HashSet<>(md.packages());
 255                     md.exports().stream().map(Exports::source).forEach(concealedPkgs::remove);
 256                     md.opens().stream().map(Opens::source).forEach(concealedPkgs::remove);
 257                     // must have the implementation class of the services it 'provides'.
 258                     if (md.provides().stream().map(Provides::providers)
 259                           .flatMap(List::stream)
 260                           .filter(p -> zf.getEntry(toBinaryName(p)) == null)
 261                           .peek(p -> error(formatMsg("error.missing.provider", p)))
 262                           .count() != 0) {
 263                         isValid = false;
 264                         return;
 265                     }
 266                     this.md = md;
 267                     this.mdName = miName;
 268                     return;
 269                 }
 270 
 271                 if (!base.name().equals(md.name())) {
 272                     errorAndInvalid(getMsg("error.validator.info.name.notequal"));

 273                 }
 274                 if (!base.requires().equals(md.requires())) {
 275                     Set<Requires> baseRequires = base.requires();
 276                     for (Requires r : md.requires()) {
 277                         if (baseRequires.contains(r))
 278                             continue;
 279                         if (r.modifiers().contains(Requires.Modifier.TRANSITIVE)) {
 280                             errorAndInvalid(getMsg("error.validator.info.requires.transitive"));

 281                         } else if (!isPlatformModule(r.name())) {
 282                             errorAndInvalid(getMsg("error.validator.info.requires.added"));

 283                         }
 284                     }
 285                     for (Requires r : baseRequires) {
 286                         Set<Requires> mdRequires = md.requires();
 287                         if (mdRequires.contains(r))
 288                             continue;
 289                         if (!isPlatformModule(r.name())) {
 290                             errorAndInvalid(getMsg("error.validator.info.requires.dropped"));

 291                         }
 292                     }
 293                 }
 294                 if (!base.exports().equals(md.exports())) {
 295                     errorAndInvalid(getMsg("error.validator.info.exports.notequal"));

 296                 }
 297                 if (!base.opens().equals(md.opens())) {
 298                     errorAndInvalid(getMsg("error.validator.info.opens.notequal"));

 299                 }
 300                 if (!base.provides().equals(md.provides())) {
 301                     errorAndInvalid(getMsg("error.validator.info.provides.notequal"));

 302                 }
 303                 if (!base.mainClass().equals(md.mainClass())) {
 304                     errorAndInvalid(formatMsg("error.validator.info.manclass.notequal",
 305                                               ze.getName()));
 306                 }
 307                 if (!base.version().equals(md.version())) {
 308                     errorAndInvalid(formatMsg("error.validator.info.version.notequal",
 309                                               ze.getName()));
 310                 }
 311             } catch (Exception x) {
 312                 errorAndInvalid(x.getMessage() + " : " + miName);


 313             }
 314         }



 315     }
 316 
 317     private boolean checkClassName(FingerPrint fp) {
 318         if (fp.className().equals(className(fp.basename()))) {

 319             return true;
 320         }
 321         error(formatMsg2("error.validator.names.mismatch",
 322                          fp.entryName(), fp.className().replace("/", ".")));
 323         return false;
 324     }
 325 
 326     private boolean checkNestedClass(FingerPrint fp, Map<String, FingerPrint> outerClasses) {
 327         if (outerClasses.containsKey(fp.outerClassName())) {



 328             return true;
 329         }
 330         // outer class was not available
 331         error(formatMsg("error.validator.isolated.nested.class", fp.entryName()));
 332         return false;
 333     }
 334 
 335     private boolean isConcealed(String className) {




 336         if (concealedPkgs.isEmpty()) {
 337             return false;
 338         }
 339         int idx = className.lastIndexOf('/');
 340         String pkgName = idx != -1 ? className.substring(0, idx).replace('/', '.') : "";
 341         return concealedPkgs.contains(pkgName);
 342     }
 343 
 344     private static boolean isPlatformModule(String name) {
 345         return name.startsWith("java.") || name.startsWith("jdk.");
 346     }
 347 
 348     private static String className(String entryName) {
 349         return entryName.endsWith(".class") ? entryName.substring(0, entryName.length() - 6) : null;
 350     }
 351 
 352     private void error(String msg) {
 353         main.error(msg);
 354     }
 355 
 356     private void errorAndInvalid(String msg) {
 357         main.error(msg);
 358         isValid = false;
 359     }
 360 
 361     private void warn(String msg) {
 362         main.warn(msg);
 363     }

 364 }