| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.maven; |
| |
| import com.sun.source.tree.ModuleTree; |
| import com.sun.source.tree.Tree; |
| import com.sun.source.util.TreePath; |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.URL; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.LinkedHashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import java.util.stream.Collectors; |
| import javax.lang.model.element.ModuleElement; |
| import org.apache.maven.artifact.Artifact; |
| import org.apache.maven.project.MavenProject; |
| import org.netbeans.api.java.source.JavaSource; |
| import org.netbeans.api.java.source.SourceUtils; |
| import org.netbeans.api.java.source.TreeMaker; |
| import org.netbeans.api.java.source.WorkingCopy; |
| import static org.netbeans.modules.maven.classpath.ClassPathProviderImpl.MODULE_INFO_JAVA; |
| import org.openide.filesystems.FileChangeAdapter; |
| import org.openide.filesystems.FileEvent; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileRenameEvent; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.util.RequestProcessor; |
| |
| /** |
| * |
| * @author Tomas Stupka |
| */ |
| public class ModuleInfoSupport { |
| |
| private static final Logger LOG = Logger.getLogger(ModuleInfoSupport.class.getName()); |
| private static final RequestProcessor RP = new RequestProcessor(ModuleInfoSupport.class.getName()); |
| |
| private final DependencyType type; |
| private final NbMavenProjectImpl project; |
| private FileObject moduleInfo; |
| |
| private final Set<String> declaredModules = new HashSet<>(); |
| |
| private final FileChangeAdapter moduleInfoListener = new FileChangeAdapter() { |
| @Override public void fileDataCreated(FileEvent fe) { |
| if(MODULE_INFO_JAVA.equals(fe.getFile().getNameExt())) { |
| moduleInfoChange(); |
| } |
| } |
| @Override public void fileChanged(FileEvent fe) { |
| if(MODULE_INFO_JAVA.equals(fe.getFile().getNameExt())) { |
| moduleInfoChange(); |
| } |
| } |
| @Override public void fileDeleted(FileEvent fe) { |
| if(MODULE_INFO_JAVA.equals(fe.getFile().getNameExt())) { |
| moduleInfoChange(); |
| } |
| } |
| @Override public void fileRenamed(FileRenameEvent fe) { |
| if(MODULE_INFO_JAVA.equals(fe.getFile().getNameExt()) || MODULE_INFO_JAVA.equals(fe.getName() + "." + fe.getExt())) { |
| moduleInfoChange(); |
| } |
| } |
| }; |
| |
| public ModuleInfoSupport(NbMavenProjectImpl project, DependencyType type) { |
| this.project = project; |
| this.type = type; |
| |
| Collection<String> roots = getRoots(project.getOriginalMavenProject(), type); |
| for (String root : roots) { |
| FileUtil.addFileChangeListener(moduleInfoListener, new File(root)); |
| } |
| moduleInfo = getModuleInfo(roots); |
| if(moduleInfo != null) { |
| populateDeclaredModules(moduleInfo); |
| } |
| } |
| |
| private static Collection<String> getRoots(MavenProject mp, DependencyType type) { |
| final Collection<String> roots = type == DependencyType.TEST ? mp.getTestCompileSourceRoots() : mp.getCompileSourceRoots(); |
| return roots; |
| } |
| |
| private synchronized void moduleInfoChange() { |
| moduleInfo = getModuleInfo(); |
| populateDeclaredModules(moduleInfo); |
| } |
| |
| private synchronized void populateDeclaredModules(final FileObject moduleInfo) { |
| declaredModules.clear(); |
| if(moduleInfo != null) { |
| Set<String> dm = getDeclaredModules(moduleInfo); |
| if(dm != null) { |
| declaredModules.addAll(dm); |
| } |
| } |
| } |
| |
| public synchronized boolean canAddToModuleInfo(String name) { |
| if(moduleInfo == null || !moduleInfo.isValid()) { |
| return false; |
| } |
| return declaredModules != null ? !declaredModules.contains(name) : true; |
| } |
| |
| private FileObject getModuleInfo() { |
| MavenProject mp = project.getOriginalMavenProject(); |
| Collection<String> roots = type == DependencyType.TEST ? mp.getTestCompileSourceRoots() : mp.getCompileSourceRoots(); |
| return getModuleInfo(roots); |
| } |
| |
| static FileObject getModuleInfo(Collection<String> sourceRoots) { |
| // XXX cash me ? |
| FileObject moduleInfo = null; |
| for (String sourceRoot : sourceRoots) { |
| File file = new File(sourceRoot, MODULE_INFO_JAVA); |
| moduleInfo = FileUtil.toFileObject(file); |
| if(moduleInfo != null) { |
| break; |
| } |
| } |
| return moduleInfo; |
| } |
| |
| static Set<String> getDeclaredModules(NbMavenProjectImpl project) { |
| FileObject moduleInfo = getModuleInfo(project.getOriginalMavenProject().getCompileSourceRoots()); |
| return moduleInfo != null ? getDeclaredModules(moduleInfo) : null; |
| } |
| |
| static Set<String> getDeclaredModules(FileObject moduleInfo) { |
| JavaSource src = moduleInfo != null ? JavaSource.forFileObject(moduleInfo) : null; |
| if (src == null) { |
| return null; |
| } |
| return getDeclaredModules(src); |
| } |
| |
| private static Set<String> getDeclaredModules(final JavaSource src) { |
| Set<String> declaredModuleNames = new HashSet<>(); |
| try { |
| src.runUserActionTask((cc)-> { |
| cc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED); |
| final List<? extends Tree> decls = cc.getCompilationUnit().getTypeDecls(); |
| final ModuleElement me = !decls.isEmpty() && decls.get(0).getKind() == Tree.Kind.MODULE ? |
| (ModuleElement) cc.getTrees().getElement(TreePath.getPath(cc.getCompilationUnit(), decls.get(0))) : |
| null; |
| if (me != null) { |
| for (ModuleElement.Directive d : me.getDirectives()) { |
| if (d.getKind() == ModuleElement.DirectiveKind.REQUIRES) { |
| final ModuleElement.RequiresDirective reqD = (ModuleElement.RequiresDirective) d; |
| final String name = reqD.getDependency().getQualifiedName().toString(); |
| declaredModuleNames.add(name); |
| } |
| } |
| } |
| }, true); |
| } catch (IOException ex) { |
| LOG.log(Level.WARNING, null, ex); |
| } |
| log("Declared modules:", declaredModuleNames); // NOI18N |
| return declaredModuleNames; |
| } |
| |
| public static void addRequires(MavenProject mp, Collection<? extends Artifact> artifacts) { |
| artifacts.stream().collect(Collectors.groupingBy(DependencyType::forArtifact)) |
| .entrySet().forEach((e) -> addRequires(getModuleInfo(getRoots(mp, e.getKey())), e.getValue())); |
| } |
| |
| private static void addRequires(FileObject moduleInfo, Collection<? extends Artifact> artifacts) { |
| RP.post(() -> { |
| if(moduleInfo != null) { |
| Set<String> declaredModules = getDeclaredModules(moduleInfo); |
| List<String> newModules = new LinkedList<>(); |
| for (Artifact a : artifacts) { |
| URL url = FileUtil.urlForArchiveOrDir(a.getFile()); |
| String name = url != null ? SourceUtils.getModuleName(url) : null; |
| LOG.log(Level.FINE, "Artifact {0} has modules name ''{1}''", new Object[]{url, name}); // NOI18N |
| if(name != null) { |
| if(!declaredModules.contains(name)) { |
| newModules.add(name); |
| } |
| } else { |
| LOG.log(Level.WARNING, "Could not determine module name for artifact {0}", new Object[]{url}); // NOI18N |
| } |
| } |
| if(!newModules.isEmpty()) { |
| ModuleInfoSupport.addRequires(moduleInfo, newModules); |
| } |
| } |
| }); |
| } |
| |
| private static void addRequires(FileObject moduleInfo, List<String> newModules) { |
| final JavaSource src = JavaSource.forFileObject(moduleInfo); |
| if (src == null) { |
| return; |
| } |
| |
| Set<String> declaredModuleNames = getDeclaredModules(src); |
| Set<String> requiredModuleNames = new LinkedHashSet<>(); |
| for (String name : newModules) { |
| if (name != null && !declaredModuleNames.contains(name)) { |
| requiredModuleNames.add(name); |
| } |
| } |
| |
| log("To be addded modules:", requiredModuleNames); // NOI18N |
| if (!requiredModuleNames.isEmpty()) { |
| final Set<String> mNames = requiredModuleNames; |
| try { |
| src.runModificationTask((WorkingCopy copy) -> { |
| copy.toPhase(JavaSource.Phase.RESOLVED); |
| TreeMaker tm = copy.getTreeMaker(); |
| ModuleTree modle = (ModuleTree) copy.getCompilationUnit().getTypeDecls().get(0); |
| ModuleTree newModle = modle; |
| for (String mName : mNames) { |
| newModle = tm.addModuleDirective(newModle, tm.Requires(false, false, tm.QualIdent(mName))); |
| } |
| copy.rewrite(modle, newModle); |
| }).commit(); |
| } catch (IOException ex) { |
| LOG.log(Level.WARNING, null, ex); |
| } |
| } |
| } |
| |
| static void log(String prefix, Collection cls) { |
| if(LOG.isLoggable(Level.FINE)) { |
| LOG.log(Level.FINE, prefix); |
| if(cls.isEmpty()) { |
| LOG.log(Level.FINE, " EMPTY"); // NOI18N |
| } else { |
| for (Object o : cls) { |
| LOG.log(Level.FINE, " {0}", o.toString()); // NOI18N |
| } |
| } |
| } |
| } |
| } |