525251 : add Java 9 module flag to classpath dependencies

Changes the Java configurator so that, if a Java project is a JPMS module, all classpath dependencies
which match required modules in module-info.java will have the module flage set to true.

One caveat is that, you currently need to call Maven Update project configuration after updating the
required modules in module-info.java, so the module flag can be added to the dependencies. See [2].

This change requires APIs from unreleased JDT (install from [1]) to compile, but m2e can still be installed
and run on older JDT versions.

[1] http://download.eclipse.org/eclipse/updates/4.8-I-builds
[2] https://dev.eclipse.org/mhonarc/lists/m2e-dev/msg02068.html

Change-Id: I639f08ff7c32ea7b59b361935886204720942ee8
Signed-off-by: Fred Bricon <fbricon@gmail.com>
diff --git a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java
index 645d695..1acdf1a 100644
--- a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java
+++ b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java
@@ -61,7 +61,8 @@
  * 
  * @author igor
  */
-public abstract class AbstractJavaProjectConfigurator extends AbstractProjectConfigurator {
+public abstract class AbstractJavaProjectConfigurator extends AbstractProjectConfigurator
+    implements IJavaProjectConfigurator {
 
   private static final IPath[] DEFAULT_INCLUSIONS = new IPath[0];
 
@@ -650,4 +651,14 @@
     }
     return new Path(relative.replace('\\', '/')); //$NON-NLS-1$ //$NON-NLS-2$
   }
+
+  @SuppressWarnings("restriction")
+  public void configureClasspath(IMavenProjectFacade facade, IClasspathDescriptor classpath, IProgressMonitor monitor)
+      throws CoreException {
+    ModuleSupport.configureClasspath(facade, classpath, monitor);
+  }
+
+  public void configureRawClasspath(ProjectConfigurationRequest request, IClasspathDescriptor classpath,
+      IProgressMonitor monitor) throws CoreException {
+  }
 }
diff --git a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/ModuleSupport.java b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/ModuleSupport.java
new file mode 100644
index 0000000..6f591c5
--- /dev/null
+++ b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/ModuleSupport.java
@@ -0,0 +1,173 @@
+/*******************************************************************************
+ * Copyright (c) 2017 Red Hat, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *      Red Hat, Inc. - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.m2e.jdt.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.zip.ZipFile;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.jdt.core.IClasspathAttribute;
+import org.eclipse.jdt.core.IClasspathEntry;
+import org.eclipse.jdt.core.IJavaProject;
+import org.eclipse.jdt.core.IModuleDescription;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.env.AutomaticModuleNaming;
+import org.eclipse.jdt.internal.compiler.env.IModule;
+import org.eclipse.jdt.internal.core.AbstractModule;
+
+import org.apache.maven.project.MavenProject;
+
+import org.eclipse.m2e.core.MavenPlugin;
+import org.eclipse.m2e.core.project.IMavenProjectFacade;
+import org.eclipse.m2e.jdt.IClasspathDescriptor;
+import org.eclipse.m2e.jdt.IClasspathEntryDescriptor;
+
+
+/**
+ * Helper for Java Module Support
+ *
+ * @author Fred Bricon
+ * @since 1.8.2
+ */
+@SuppressWarnings("restriction")
+public class ModuleSupport {
+
+  static final boolean IS_MODULE_SUPPORT_AVAILABLE;
+
+  private static final Logger log = LoggerFactory.getLogger(ModuleSupport.class);
+
+  static {
+    boolean isModuleSupportAvailable = false;
+    try {
+      Class.forName("org.eclipse.jdt.core.IModuleDescription");
+      isModuleSupportAvailable = true;
+    } catch(ClassNotFoundException ignored) {
+    }
+    IS_MODULE_SUPPORT_AVAILABLE = isModuleSupportAvailable;
+  }
+
+  /**
+   * Sets <code>module</code flag to <code>true</code> to classpath dependencies declared in module-info.java
+   * 
+   * @param facade a Maven facade project
+   * @param classpath a classpath descriptor
+   * @param monitor a progress monitor
+   */
+  public static void configureClasspath(IMavenProjectFacade facade, IClasspathDescriptor classpath,
+      IProgressMonitor monitor) throws CoreException {
+    if(!IS_MODULE_SUPPORT_AVAILABLE) {
+      return;
+    }
+    IJavaProject javaProject = JavaCore.create(facade.getProject());
+    IModuleDescription moduleDescription = javaProject.getModuleDescription();
+    if(!(moduleDescription instanceof AbstractModule)) {
+      return;
+    }
+    AbstractModule module = (AbstractModule) moduleDescription;
+    Set<String> requiredModules = Stream.of(module.getRequiredModules()).map(m -> new String(m.name()))
+        .collect(Collectors.toSet());
+    for(IClasspathEntryDescriptor entry : classpath.getEntryDescriptors()) {
+      String moduleName = getModuleName(entry, monitor);
+      if(requiredModules.contains(moduleName)) {
+        entry.setClasspathAttribute(IClasspathAttribute.MODULE, Boolean.TRUE.toString());
+      }
+    }
+  }
+
+  private static String getModuleName(IClasspathEntryDescriptor entry, IProgressMonitor monitor) {
+    String module = null;
+    if(IClasspathEntry.CPE_LIBRARY == entry.getEntryKind()) {
+      module = getModuleNameFromJar(entry.getPath().toFile());
+    } else if(IClasspathEntry.CPE_PROJECT == entry.getEntryKind()) {
+      module = getModuleNameFromProject(entry.getPath(), monitor);
+    }
+    return module;
+  }
+
+  private static String getModuleNameFromProject(IPath projectPath, IProgressMonitor monitor) {
+    IJavaProject project = getJavaProject(projectPath);
+    String module = null;
+    if(project != null) {
+      try {
+        if(project.getModuleDescription() == null) {
+          String buildName = null;
+          IMavenProjectFacade facade = MavenPlugin.getMavenProjectRegistry().getProject(project.getProject());
+          if(facade != null) {
+            MavenProject mavenProject = facade.getMavenProject(monitor);
+            if(mavenProject != null) {
+              buildName = mavenProject.getBuild().getFinalName();
+            }
+          }
+          if(buildName == null || buildName.isEmpty()) {
+            buildName = project.getElementName();
+          }
+          module = new String(AutomaticModuleNaming.determineAutomaticModuleName(buildName, false, null));
+        } else {
+          module = project.getModuleDescription().getElementName();
+        }
+      } catch(CoreException ex) {
+        log.error(ex.getMessage(), ex);
+      }
+    }
+    return module;
+  }
+
+  private static IJavaProject getJavaProject(IPath projectPath) {
+    if(projectPath == null || projectPath.isEmpty()) {
+      return null;
+    }
+    IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
+    IProject project = root.getProject(projectPath.lastSegment());
+    if(project.isAccessible()) {
+      return JavaCore.create(project);
+    }
+    return null;
+  }
+
+  private static String getModuleNameFromJar(File file) {
+    if(!file.isFile()) {
+      return null;
+    }
+    char[] moduleName = null;
+    try (ZipFile zipFile = new ZipFile(file)) {
+      IModule module = null;
+      ClassFileReader reader = ClassFileReader.read(zipFile, IModule.MODULE_INFO_CLASS);
+      if(reader != null) {
+        module = reader.getModuleDeclaration();
+        if(module != null) {
+          moduleName = module.name();
+        }
+      }
+    } catch(ClassFormatException | IOException ex) {
+      log.error(ex.getMessage(), ex);
+    }
+    if(moduleName == null) {
+      moduleName = AutomaticModuleNaming.determineAutomaticModuleName(file.getAbsolutePath());
+    }
+    return new String(moduleName);
+  }
+
+}
diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegate.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegate.java
index 6bc660d..c1ba264 100644
--- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegate.java
+++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegate.java
@@ -113,6 +113,12 @@
     return cp.toArray(new String[cp.size()]);
   }
 
+  public String[][] getClasspathAndModulepath(ILaunchConfiguration configuration) throws CoreException {
+    String[][] paths = new String[2][];
+    paths[0] = getClasspath(configuration);
+    return paths;
+  }
+
   public String getProgramArguments(ILaunchConfiguration configuration) throws CoreException {
     if(programArguments == null) {
       String goals = getGoals(configuration);