blob: 45027bba6ae238659d3a44875f341b6ffc9520b6 [file] [log] [blame]
/*
* 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.execute;
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.java.queries.UnitTestForSourceQuery;
import org.netbeans.api.java.source.ClasspathInfo;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.Sources;
import org.netbeans.modules.maven.NbMavenProjectImpl;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.modules.maven.classpath.MavenSourcesImpl;
import org.netbeans.modules.maven.configurations.M2ConfigProvider;
import org.netbeans.modules.maven.spi.actions.ActionConvertor;
import org.netbeans.modules.maven.spi.actions.ReplaceTokenProvider;
import org.netbeans.spi.project.ActionProvider;
import org.netbeans.spi.project.ProjectServiceProvider;
import org.netbeans.spi.project.SingleMethod;
import org.netbeans.spi.project.support.ant.EditableProperties;
import org.netbeans.spi.project.support.ant.PropertyUtils;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.URLMapper;
import org.openide.loaders.DataObject;
import org.openide.util.Lookup;
import org.openide.util.Utilities;
/**
*
* @author mkleint
*/
@ProjectServiceProvider(service={ReplaceTokenProvider.class, ActionConvertor.class}, projectType="org-netbeans-modules-maven")
public class DefaultReplaceTokenProvider implements ReplaceTokenProvider, ActionConvertor {
private static final String ARTIFACTID = "artifactId";//NOI18N
private static final String CLASSPATHSCOPE = "classPathScope";//NOI18N
private static final String GROUPID = "groupId";//NOI18N
private final Project project;
static final String CLASSNAME = "className";//NOI18N
static final String CLASSNAME_EXT = "classNameWithExtension";//NOI18N
static final String PACK_CLASSNAME = "packageClassName";//NOI18N
static final String ABSOLUTE_PATH = "absolutePathName";
public static final String METHOD_NAME = "nb.single.run.methodName"; //NOI18N
private static final String VARIABLE_PREFIX = "var."; //NOI18N
// as defined in org.netbeans.modules.project.ant.VariablesModel
public static String[] fileBasedProperties = new String[] {
PACK_CLASSNAME, CLASSNAME, CLASSNAME_EXT, ABSOLUTE_PATH
};
public DefaultReplaceTokenProvider(Project prj) {
project = prj;
}
private static FileObject[] extractFileObjectsfromLookup(Lookup lookup) {
List<FileObject> files = new ArrayList<FileObject>(lookup.lookupAll(FileObject.class));
if (files.isEmpty()) { // fallback to old nodes
for (DataObject d : lookup.lookupAll(DataObject.class)) {
files.add(d.getPrimaryFile());
}
}
Collection<? extends SingleMethod> methods = lookup.lookupAll(SingleMethod.class);
if (methods.size() == 1) {
SingleMethod method = methods.iterator().next();
files.add(method.getFile());
}
return files.toArray(new FileObject[files.size()]);
}
@Override public Map<String, String> createReplacements(String actionName, Lookup lookup) {
FileObject[] fos = extractFileObjectsfromLookup(lookup);
SourceGroup group = findGroup(ProjectUtils.getSources(project).getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA), fos);
HashMap<String, String> replaceMap = new HashMap<String, String>();
// read environment variables in the IDE and prefix them with "env." just in case someone uses it as variable in the action mappings
for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
replaceMap.put(MavenCommandLineExecutor.ENV_PREFIX + entry.getKey(), entry.getValue());
}
if (fos.length > 0) {
replaceMap.put(ABSOLUTE_PATH, FileUtil.toFile(fos[0]).getAbsolutePath());
}
//read global variables defined in the IDE
Map<String, String> vars = readVariables();
replaceMap.putAll(vars);
//read active configuration properties..
Map<String, String> configProps = project.getLookup().lookup(M2ConfigProvider.class).getActiveConfiguration().getProperties();
replaceMap.putAll(configProps);
NbMavenProject prj = project.getLookup().lookup(NbMavenProject.class);
replaceMap.put(GROUPID, prj.getMavenProject().getGroupId());
replaceMap.put(ARTIFACTID, prj.getMavenProject().getArtifactId());
StringBuilder packClassname = new StringBuilder();
StringBuilder classname = new StringBuilder();
StringBuilder classnameExt = new StringBuilder();
if (group != null) {
boolean first = true;
boolean isTest = false;
Set<String> uniqueClassNames = new HashSet<String>(fos.length);
for (FileObject file : fos) {
if (first) {
first = false;
} else {
if (!isTest && !(ActionProvider.COMMAND_TEST_SINGLE.equals(actionName) ||
ActionProvider.COMMAND_DEBUG_TEST_SINGLE.equals(actionName) ||
ActionProvider.COMMAND_PROFILE_TEST_SINGLE.equals(actionName) ||
ActionProvider.COMMAND_TEST.equals(actionName))) {
// Execution can not have more files separated by commas. Only test can.
break;
} else {
isTest = true;
}
packClassname.append(',');
classname.append(',');
classnameExt.append(',');
}
if (file.isFolder()) {
String rel = FileUtil.getRelativePath(group.getRootFolder(), file);
assert rel != null;
String pkg = rel.replace('/', '.');
if (!pkg.isEmpty()) {
packClassname.append(pkg).append(".**."); // test everything under this package recusively
}
packClassname.append("*");
if (ActionProvider.COMMAND_TEST_SINGLE.equals(actionName) || ActionProvider.COMMAND_DEBUG_TEST_SINGLE.equals(actionName)) {
packClassname.append("Test");
}
classname.append(pkg); // ?
classnameExt.append(pkg); // ??
} else { // XXX do we need to limit to text/x-java? What about files of other type?
String relP = FileUtil.getRelativePath(group.getRootFolder(), file.getParent());
assert relP != null;
StringBuilder cn = new StringBuilder();
if (!relP.isEmpty()) {
cn.append(relP.replace('/', '.')).append('.');
}
String n = file.getName();
cn.append(n);
if (uniqueClassNames.add(cn.toString())) {
packClassname.append(cn);
classname.append(n);
} else {
packClassname.deleteCharAt(packClassname.length() - 1); // Delete the comma
classname.deleteCharAt(classname.length() - 1);
}
classnameExt.append(file.getNameExt());
if (MavenSourcesImpl.NAME_SOURCE.equals(group.getName()) &&
(ActionProvider.COMMAND_TEST_SINGLE.equals(actionName) ||
ActionProvider.COMMAND_DEBUG_TEST_SINGLE.equals(actionName) ||
ActionProvider.COMMAND_PROFILE_TEST_SINGLE.equals(actionName))) {
String fix = "Test";
if (classnameExt.toString().endsWith("." + file.getExt())) {
classnameExt.delete(classnameExt.length() - ("." + file.getExt()).length(), classnameExt.length());
URL[] unitRoots = UnitTestForSourceQuery.findUnitTests(group.getRootFolder());
if (unitRoots != null) {
for (URL unitRoot : unitRoots) {
FileObject root = URLMapper.findFileObject(unitRoot);
if (root != null) { //#237312
String ngPath = relP + (relP.isEmpty() ? "" : "/") + classnameExt + "NGTest." + file.getExt();
if (root.getFileObject(ngPath) != null) {
fix = "NGTest";
break;
}
}
}
}
classnameExt.append(fix).append(".").append(file.getExt());
}
packClassname.append(fix);
classname.append(fix);
}
}
}
} else {
// not all of the selected files are under one source root, so maybe they were
// selected from both source and test packages and "Test Files" action was invoked on them?
if (ActionProvider.COMMAND_TEST_SINGLE.equals(actionName) ||
ActionProvider.COMMAND_DEBUG_TEST_SINGLE.equals(actionName))
{
HashSet<String> test = new HashSet<String>();
addSelectedFiles(false, fos, test);
addSelectedFiles(true, fos, test);
String files2test = test.toString().replace(" ", "");
packClassname.append(files2test.substring(1, files2test.length() - 1));
}
}
if (packClassname.length() > 0) { //#213671
replaceMap.put(PACK_CLASSNAME, packClassname.toString());
}
if (classname.length() > 0) { //#213671
replaceMap.put(CLASSNAME, classname.toString());
}
if (classnameExt.length() > 0) { //#213671
replaceMap.put(CLASSNAME_EXT, classnameExt.toString());
}
Collection<? extends SingleMethod> methods = lookup.lookupAll(SingleMethod.class);
if (methods.size() == 1) {
//sort of hack to push the method name through the current apis..
SingleMethod method = methods.iterator().next();
replaceMap.put(METHOD_NAME, method.getMethodName());
}
if (group != null &&
//TODO not nice, how to figure in a better way? by source classpath?
(MavenSourcesImpl.NAME_TESTSOURCE.equals(group.getName()))) {
replaceMap.put(CLASSPATHSCOPE,"test"); //NOI18N
} else {
replaceMap.put(CLASSPATHSCOPE,"runtime"); //NOI18N
}
return replaceMap;
}
private void addSelectedFiles(boolean testRoots, FileObject[] candidates, HashSet<String> test) {
NbMavenProjectImpl prj = project.getLookup().lookup(NbMavenProjectImpl.class);
if (prj != null) {
URI[] roots = prj.getSourceRoots(testRoots);
for (URI uri : roots) {
FileObject root = FileUtil.toFileObject(Utilities.toFile(uri));
// test if root isn't null - NbMavenProjectImpl.getSourceRoots() might return a bogus
// non test uri in case there are only test source roots.
// NOTE that not sure if this is generaly the right place for the fix. Even though it is
// MavenProject which returns those uris, not sure if e.g. that behaviour wasn't somewhere on the way overriden
// by the nb maven module ...
if(root != null) {
for (FileObject candidate : candidates) {
String relativePath = FileUtil.getRelativePath(root, candidate);
if (relativePath != null) {
if (testRoots) {
relativePath = relativePath.replace(".java", "").replace('/', '.'); //NOI18N
} else {
relativePath = relativePath.replace(".java", "Test").replace('/', '.'); //NOI18N
}
test.add(relativePath);
}
}
}
}
}
}
/** Finds the one source group, if any, which contains all of the listed files. */
private static @CheckForNull SourceGroup findGroup(SourceGroup[] groups, FileObject[] files) {
SourceGroup selected = null;
for (FileObject file : files) {
for (SourceGroup group : groups) {
FileObject root = group.getRootFolder();
if (file == root || FileUtil.isParentOf(root, file)) { // or group.contains(file)?
if (selected == null) {
selected = group;
} else if (selected != group) {
return null;
}
}
}
}
return selected;
}
public static Map<String, String> readVariables() {
Map<String, String> vs = new HashMap<String, String>();
EditableProperties ep = PropertyUtils.getGlobalProperties();
for (Map.Entry<String, String> entry : ep.entrySet()) {
if (entry.getKey().startsWith(VARIABLE_PREFIX)) {
vs.put(entry.getKey().substring(VARIABLE_PREFIX.length()), FileUtil.normalizeFile(new File(entry.getValue())).getAbsolutePath());
}
}
return vs;
}
// /*
// * copied from ActionUtils and reworked so that it checks for mimeType of files, and DOES NOT include files with suffix 'suffix'
// */
// private static FileObject[] findSelectedFilesByMimeType(Lookup context, FileObject dir, String mimeType, String suffix, boolean strict) {
// if (dir != null && !dir.isFolder()) {
// throw new IllegalArgumentException("Not a folder: " + dir); // NOI18N
// }
//
// List<FileObject> files = new ArrayList<FileObject>();
// for (DataObject d : context.lookupAll(DataObject.class)) {
// FileObject f = d.getPrimaryFile();
// boolean matches = FileUtil.toFile(f) != null;
// if (dir != null) {
// matches &= (FileUtil.isParentOf(dir, f) || dir == f);
// }
// if (mimeType != null) {
// matches &= f.getMIMEType().equals(mimeType);
// }
// if (suffix != null) {
// matches &= !f.getNameExt().endsWith(suffix);
// }
// // Generally only files from one project will make sense.
// // Currently the action UI infrastructure (PlaceHolderAction)
// // checks for that itself. Should there be another check here?
// if (matches) {
// files.add(f);
// } else if (strict) {
// return null;
// }
// }
// if (files.isEmpty()) {
// return null;
// }
// return files.toArray(
// new FileObject[files.size()]);
// }
@Override public String convert(String action, Lookup lookup) {
if (SingleMethod.COMMAND_DEBUG_SINGLE_METHOD.equals(action)) {
return ActionProvider.COMMAND_DEBUG_TEST_SINGLE;
}
if (SingleMethod.COMMAND_RUN_SINGLE_METHOD.equals(action)) {
return ActionProvider.COMMAND_TEST_SINGLE;
}
if (ActionProvider.COMMAND_RUN_SINGLE.equals(action) ||
ActionProvider.COMMAND_DEBUG_SINGLE.equals(action) ||
ActionProvider.COMMAND_PROFILE_SINGLE.equals(action)) {
FileObject[] fos = extractFileObjectsfromLookup(lookup);
if (fos.length > 0) {
FileObject fo = fos[0];
if ("text/x-java".equals(fo.getMIMEType())) {//NOI18N
Sources srcs = ProjectUtils.getSources(project);
SourceGroup[] grp = srcs.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
for (int i = 0; i < grp.length; i++) {
String relPath = FileUtil.getRelativePath(grp[i].getRootFolder(), fo);
if (relPath != null) {
if (SourceUtils.isMainClass(relPath.replaceFirst("[.]java$", "").replace('/', '.'), ClasspathInfo.create(fo), true)) {
return action + ".main";//NOI18N
}
}
}
}
}
}
return null;
}
}