Bug 540274 - m2e doesn't put multi-release jar files on the module path

Change-Id: I49461d21d356d5ab99b6c9dba37bd7dbe23659a8
Signed-off-by: Till Brychcy <register.eclipse@brychcy.de>
diff --git a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/InternalModuleSupport.java b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/InternalModuleSupport.java
index bcd2757..4198b40 100644
--- a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/InternalModuleSupport.java
+++ b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/InternalModuleSupport.java
@@ -21,9 +21,10 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
-import java.util.zip.ZipFile;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -83,9 +84,9 @@
   /**
    * Sets <code>module</code flag to <code>true</code> to classpath dependencies declared in module-info.java
    * 
-   * @param facade    a Maven facade project
+   * @param facade a Maven facade project
    * @param classpath a classpath descriptor
-   * @param monitor   a progress monitor
+   * @param monitor a progress monitor
    */
   public static void configureClasspath(IMavenProjectFacade facade, IClasspathDescriptor classpath,
       IProgressMonitor monitor) throws CoreException {
@@ -94,6 +95,22 @@
       return;
     }
 
+    int targetCompliance = 8;
+    String option = javaProject.getOption(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, true);
+    if(option != null) {
+      if(option.startsWith("1.")) {
+        option = option.substring("1.".length());
+      }
+      try {
+        targetCompliance = Integer.parseInt(option);
+      } catch(NumberFormatException ex) {
+        log.error(ex.getMessage(), ex);
+      }
+    }
+    if(targetCompliance < 9) {
+      return;
+    }
+
     if(monitor == null) {
       monitor = new NullProgressMonitor();
     }
@@ -110,13 +127,13 @@
       if(monitor.isCanceled()) {
         return;
       }
-      String moduleName = getModuleName(entry.getEntryKind(), entry.getPath(), monitor);
+      String moduleName = getModuleName(entry.getEntryKind(), entry.getPath(), monitor, targetCompliance);
       moduleMap.put(moduleName, entry);//potentially suppresses duplicate entries from the same workspace project, with different classifiers
       descriptorsMap.put(entry, moduleName);
     }
 
     Set<String> visitedModules = new HashSet<>(entryDescriptors.size());
-    collectTransitiveRequiredModules(requiredModules, visitedModules, moduleMap, monitor);
+    collectTransitiveRequiredModules(requiredModules, visitedModules, moduleMap, monitor, targetCompliance);
 
     if(monitor.isCanceled()) {
       return;
@@ -130,7 +147,8 @@
   }
 
   private static void collectTransitiveRequiredModules(Set<String> requiredModules, Set<String> visitedModules,
-      Map<String, IClasspathEntryDescriptor> moduleMap, IProgressMonitor monitor) throws JavaModelException {
+      Map<String, IClasspathEntryDescriptor> moduleMap, IProgressMonitor monitor, int targetCompliance)
+      throws JavaModelException {
     if(monitor.isCanceled() || requiredModules.isEmpty()) {
       return;
     }
@@ -140,22 +158,22 @@
         //already checked that module
         continue;
       }
-      Set<String> modules = getRequiredModules(moduleMap.get(req), monitor);
+      Set<String> modules = getRequiredModules(moduleMap.get(req), monitor, targetCompliance);
       transitiveModules.addAll(modules);
       visitedModules.add(req);
     }
     transitiveModules.removeAll(visitedModules);
     if(!transitiveModules.isEmpty()) {
       requiredModules.addAll(transitiveModules);
-      collectTransitiveRequiredModules(transitiveModules, visitedModules, moduleMap, monitor);
+      collectTransitiveRequiredModules(transitiveModules, visitedModules, moduleMap, monitor, targetCompliance);
     }
   }
 
-  private static Set<String> getRequiredModules(IClasspathEntryDescriptor entry, IProgressMonitor monitor)
-      throws JavaModelException {
+  private static Set<String> getRequiredModules(IClasspathEntryDescriptor entry, IProgressMonitor monitor,
+      int targetCompliance) throws JavaModelException {
     if(entry != null && !monitor.isCanceled()) {
       if(IClasspathEntry.CPE_LIBRARY == entry.getEntryKind()) {
-        return getRequiredModules(entry.getPath().toFile());
+        return getRequiredModules(entry.getPath().toFile(), targetCompliance);
       } else if(IClasspathEntry.CPE_PROJECT == entry.getEntryKind()) {
         return getRequiredModules(getJavaProject(entry.getPath()), monitor);
       }
@@ -173,20 +191,36 @@
     return Collections.emptySet();
   }
 
-  private static Set<String> getRequiredModules(File file) {
+  private static Set<String> getRequiredModules(File file, int targetCompliance) {
     if(!file.isFile()) {
       return Collections.emptySet();
     }
-    try (ZipFile zipFile = new ZipFile(file)) {
+    try (JarFile jar = new JarFile(file, false)) {
+      Manifest manifest = jar.getManifest();
+      boolean isMultiRelease = false;
+      if(manifest != null) {
+        isMultiRelease = "true".equalsIgnoreCase(manifest.getMainAttributes().getValue("Multi-Release"));
+      }
       IModule module = null;
-      ClassFileReader reader = ClassFileReader.read(zipFile, IModule.MODULE_INFO_CLASS);
-      if(reader != null) {
-        module = reader.getModuleDeclaration();
-        if(module != null) {
-          IModuleReference[] moduleRefs = module.requires();
-          if(moduleRefs != null) {
-            return Stream.of(moduleRefs).map(m -> new String(m.name()))
-                .collect(Collectors.toCollection(LinkedHashSet::new));
+      int compliance = isMultiRelease ? targetCompliance : 8;
+      for(int i = compliance; i >= 8; i-- ) {
+        String filename;
+        if(i == 8) {
+          // 8 represents unversioned module-info.class
+          filename = IModule.MODULE_INFO_CLASS;
+        } else {
+          filename = "META-INF/versions/" + i + "/" + IModule.MODULE_INFO_CLASS;
+        }
+        ClassFileReader reader = ClassFileReader.read(jar, filename);
+        if(reader != null) {
+          module = reader.getModuleDeclaration();
+          if(module != null) {
+            IModuleReference[] moduleRefs = module.requires();
+            if(moduleRefs != null) {
+              return Stream.of(moduleRefs).map(m -> new String(m.name()))
+                  .collect(Collectors.toCollection(LinkedHashSet::new));
+            }
+            return Collections.emptySet();
           }
         }
       }
@@ -196,11 +230,11 @@
     return Collections.emptySet();
   }
 
-  public static String getModuleName(int entryKind, IPath entryPath, IProgressMonitor monitor) {
+  public static String getModuleName(int entryKind, IPath entryPath, IProgressMonitor monitor, int targetCompliance) {
     String module = null;
     if(entryPath != null) {
       if(IClasspathEntry.CPE_LIBRARY == entryKind) {
-        module = getModuleName(entryPath.toFile());
+        module = getModuleName(entryPath.toFile(), targetCompliance);
       } else if(IClasspathEntry.CPE_PROJECT == entryKind) {
         module = getModuleName(getJavaProject(entryPath), monitor);
       }
@@ -247,28 +281,51 @@
     return null;
   }
 
-  private static String getModuleName(File file) {
+  private static String getModuleName(File file, int targetCompliance) {
     if(!file.isFile()) {
       return null;
     }
 
-    char[] moduleName = null;
-    try (ZipFile zipFile = new ZipFile(file)) {
+    try (JarFile jar = new JarFile(file, false)) {
+      Manifest manifest = jar.getManifest();
+      boolean isMultiRelease = false;
+      if(manifest != null) {
+        isMultiRelease = "true".equalsIgnoreCase(manifest.getMainAttributes().getValue("Multi-Release"));
+      }
       IModule module = null;
-      ClassFileReader reader = ClassFileReader.read(zipFile, IModule.MODULE_INFO_CLASS);
-      if(reader != null) {
-        module = reader.getModuleDeclaration();
-        if(module != null) {
-          moduleName = module.name();
+      int compliance = isMultiRelease ? targetCompliance : 8;
+      for(int i = compliance; i >= 8; i-- ) {
+        String filename;
+        if(i == 8) {
+          // 8 represents unversioned module-info.class
+          filename = IModule.MODULE_INFO_CLASS;
+        } else {
+          filename = "META-INF/versions/" + i + "/" + IModule.MODULE_INFO_CLASS;
+        }
+        ClassFileReader reader = ClassFileReader.read(jar, filename);
+        if(reader != null) {
+          module = reader.getModuleDeclaration();
+          if(module != null) {
+            char[] moduleName = module.name();
+            if(moduleName != null) {
+              return new String(moduleName);
+            }
+          }
+        }
+      }
+      if(manifest != null) {
+        // optimization: we already have the manifest, so directly check for Automatic-Module-Name
+        // rather than using AutomaticModuleNaming.determineAutomaticModuleName(String)
+        String automaticModuleName = manifest.getMainAttributes().getValue("Automatic-Module-Name");
+        if(automaticModuleName != null) {
+          return automaticModuleName;
         }
       }
     } catch(ClassFormatException | IOException ex) {
       log.error(ex.getMessage(), ex);
     }
-    if(moduleName == null) {
-      moduleName = AutomaticModuleNaming.determineAutomaticModuleName(file.getAbsolutePath());
-    }
-    return new String(moduleName);
+    return new String(
+        AutomaticModuleNaming.determineAutomaticModuleNameFromFileName(file.getAbsolutePath(), true, true));
   }
 
   public static boolean isModuleEntry(IClasspathEntry entry) {