package sun.tools.jar;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Exports;
import java.lang.module.ModuleDescriptor.Opens;
import java.lang.module.ModuleDescriptor.Provides;
import java.lang.module.ModuleDescriptor.Requires;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import static java.util.jar.JarFile.MANIFEST_NAME;
import static sun.tools.jar.Main.VERSIONS_DIR;
import static sun.tools.jar.Main.VERSIONS_DIR_LENGTH;
import static sun.tools.jar.Main.MODULE_INFO;
import static sun.tools.jar.Main.getMsg;
import static sun.tools.jar.Main.formatMsg;
import static sun.tools.jar.Main.formatMsg2;
import static sun.tools.jar.Main.toBinaryName;
final class Validator {
private final Map<String,FingerPrint> classes = new HashMap<>();
private final Main main;
private final ZipFile zf;
private boolean isValid = true;
private Set<String> concealedPkgs = Collections.emptySet();
private ModuleDescriptor md;
private String mdName;
private Validator(Main main, ZipFile zf) {
this.main = main;
this.zf = zf;
checkModuleDescriptor(MODULE_INFO);
}
static boolean validate(Main main, ZipFile zf) throws IOException {
return new Validator(main, zf).validate();
}
private boolean validate() {
try {
zf.stream()
.filter(e -> e.getName().endsWith(".class"))
.map(this::getFingerPrint)
.filter(FingerPrint::isClass)
.collect(Collectors.groupingBy(
FingerPrint::mrversion,
TreeMap::new,
Collectors.toMap(FingerPrint::className,
Function.identity(),
this::sameNameFingerPrint)))
.forEach((version, entries) -> {
if (version == 0)
validateBase(entries);
else
validateVersioned(entries);
});
} catch (InvalidJarException e) {
errorAndInvalid(e.getMessage());
}
return isValid;
}
static class InvalidJarException extends RuntimeException {
private static final long serialVersionUID = -3642329147299217726L;
InvalidJarException(String msg) {
super(msg);
}
}
private FingerPrint sameNameFingerPrint(FingerPrint fp1, FingerPrint fp2) {
checkClassName(fp1);
checkClassName(fp2);
return fp2;
}
private FingerPrint getFingerPrint(ZipEntry ze) {
String ename = ze.getName();
String bname = ename;
int version = 0;
if (ename.startsWith(VERSIONS_DIR)) {
int n = ename.indexOf("/", VERSIONS_DIR_LENGTH);
if (n == -1) {
throw new InvalidJarException(
formatMsg("error.validator.version.notnumber", ename));
}
try {
version = Integer.parseInt(ename, VERSIONS_DIR_LENGTH, n, 10);
} catch (NumberFormatException x) {
throw new InvalidJarException(
formatMsg("error.validator.version.notnumber", ename));
}
if (n == ename.length()) {
throw new InvalidJarException(
formatMsg("error.validator.entryname.tooshort", ename));
}
bname = ename.substring(n + 1);
}
try (InputStream is = zf.getInputStream(ze)) {
return new FingerPrint(bname, ename, version, is.readAllBytes());
} catch (IOException x) {
throw new InvalidJarException(x.getMessage());
}
}
public void validateBase(Map<String, FingerPrint> fps) {
fps.values().forEach( fp -> {
if (!checkClassName(fp)) {
return;
}
if (fp.isNestedClass()) {
checkNestedClass(fp, fps);
}
classes.put(fp.className(), fp);
});
}
public void validateVersioned(Map<String, FingerPrint> fps) {
fps.values().forEach( fp -> {
if (MODULE_INFO.equals(fp.basename())) {
checkModuleDescriptor(fp.entryName());
return;
}
FingerPrint matchFp = classes.get(fp.className());
if (matchFp == null) {
if (fp.isNestedClass()) {
checkNestedClass(fp, fps);
return;
}
if (fp.isPublicClass()) {
if (!isConcealed(fp.className())) {
errorAndInvalid(formatMsg("error.validator.new.public.class",
fp.entryName()));
return;
}
warn(formatMsg("warn.validator.concealed.public.class",
fp.entryName()));
}
classes.put(fp.className(), fp);
return;
}
if (fp.isIdentical(matchFp)) {
warn(formatMsg("warn.validator.identical.entry", fp.entryName()));
return;
}
if (fp.isNestedClass()) {
checkNestedClass(fp, fps);
return;
}
if (!fp.isCompatibleVersion(matchFp)) {
errorAndInvalid(formatMsg("error.validator.incompatible.class.version",
fp.entryName()));
return;
}
if (!fp.isSameAPI(matchFp)) {
errorAndInvalid(formatMsg("error.validator.different.api",
fp.entryName()));
return;
}
if (!checkClassName(fp)) {
return;
}
classes.put(fp.className(), fp);
return;
});
}
private void checkModuleDescriptor(String miName) {
ZipEntry ze = zf.getEntry(miName);
if (ze != null) {
try (InputStream jis = zf.getInputStream(ze)) {
ModuleDescriptor md = ModuleDescriptor.read(jis);
ModuleDescriptor base = this.md;
if (base == null) {
concealedPkgs = new HashSet<>(md.packages());
md.exports().stream().map(Exports::source).forEach(concealedPkgs::remove);
md.opens().stream().map(Opens::source).forEach(concealedPkgs::remove);
if (md.provides().stream().map(Provides::providers)
.flatMap(List::stream)
.filter(p -> zf.getEntry(toBinaryName(p)) == null)
.peek(p -> error(formatMsg("error.missing.provider", p)))
.count() != 0) {
isValid = false;
return;
}
this.md = md;
this.mdName = miName;
return;
}
if (!base.name().equals(md.name())) {
errorAndInvalid(getMsg("error.validator.info.name.notequal"));
}
if (!base.requires().equals(md.requires())) {
Set<Requires> baseRequires = base.requires();
for (Requires r : md.requires()) {
if (baseRequires.contains(r))
continue;
if (r.modifiers().contains(Requires.Modifier.TRANSITIVE)) {
errorAndInvalid(getMsg("error.validator.info.requires.transitive"));
} else if (!isPlatformModule(r.name())) {
errorAndInvalid(getMsg("error.validator.info.requires.added"));
}
}
for (Requires r : baseRequires) {
Set<Requires> mdRequires = md.requires();
if (mdRequires.contains(r))
continue;
if (!isPlatformModule(r.name())) {
errorAndInvalid(getMsg("error.validator.info.requires.dropped"));
}
}
}
if (!base.exports().equals(md.exports())) {
errorAndInvalid(getMsg("error.validator.info.exports.notequal"));
}
if (!base.opens().equals(md.opens())) {
errorAndInvalid(getMsg("error.validator.info.opens.notequal"));
}
if (!base.provides().equals(md.provides())) {
errorAndInvalid(getMsg("error.validator.info.provides.notequal"));
}
if (!base.mainClass().equals(md.mainClass())) {
errorAndInvalid(formatMsg("error.validator.info.manclass.notequal",
ze.getName()));
}
if (!base.version().equals(md.version())) {
errorAndInvalid(formatMsg("error.validator.info.version.notequal",
ze.getName()));
}
} catch (Exception x) {
errorAndInvalid(x.getMessage() + " : " + miName);
}
}
}
private boolean checkClassName(FingerPrint fp) {
if (fp.className().equals(className(fp.basename()))) {
return true;
}
error(formatMsg2("error.validator.names.mismatch",
fp.entryName(), fp.className().replace("/", ".")));
return isValid = false;
}
private boolean checkNestedClass(FingerPrint fp, Map<String, FingerPrint> outerClasses) {
if (outerClasses.containsKey(fp.outerClassName())) {
return true;
}
error(formatMsg("error.validator.isolated.nested.class", fp.entryName()));
return isValid = false;
}
private boolean isConcealed(String className) {
if (concealedPkgs.isEmpty()) {
return false;
}
int idx = className.lastIndexOf('/');
String pkgName = idx != -1 ? className.substring(0, idx).replace('/', '.') : "";
return concealedPkgs.contains(pkgName);
}
private static boolean isPlatformModule(String name) {
return name.startsWith("java.") || name.startsWith("jdk.");
}
private static String className(String entryName) {
return entryName.endsWith(".class") ? entryName.substring(0, entryName.length() - 6) : null;
}
private void error(String msg) {
main.error(msg);
}
private void errorAndInvalid(String msg) {
main.error(msg);
isValid = false;
}
private void warn(String msg) {
main.warn(msg);
}
}