/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2012 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. * * Contributor(s): * * Portions Copyrighted 2012 Sun Microsystems, Inc. */ package ca.weblite.netbeans.mirah.antproject.base; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import org.apache.tools.ant.module.api.support.ActionUtils; import org.netbeans.api.java.project.JavaProjectConstants; import org.netbeans.api.java.source.ui.ScanDialog; 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.spi.project.ActionProvider; import org.netbeans.spi.project.support.ant.GeneratedFilesHelper; import org.netbeans.spi.project.support.ant.PropertyUtils; import org.openide.DialogDisplayer; import org.openide.ErrorManager; import org.openide.NotifyDescriptor; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.Lookup; import org.openide.util.NbBundle; /** * Base ActionProvider for each Ant based project. Support few basic operations: * - Compile File * - Run File * - Test File * - Debug File * - Debug Test File * * Implementors should create own map of the supported actions for a certain * type of project. The reason why this cannot be done in general is that we * have different ant target names in a different types of project (e.g. for * debugging single file there is a "debug-single-main" command in the Web * project, but "debug-single" command in the J2SE project). * * @author Martin Janicek */ public abstract class AbstractMirahActionProvider implements ActionProvider { // from J2SEProjectProperties public static final String BUILD_SCRIPT = "buildfile"; // NOI18N // from J2SEConfigurationProvider public static final String PROP_CONFIG = "config"; // NOI18N /** Map from commands to mirah targets */ private final Map<String, String> supportedActions; private final Project project; private boolean overrideTestTarget = true; public AbstractMirahActionProvider(Project project) { this.project = project; FileObject destDirFO = project.getProjectDirectory().getFileObject("nbproject"); // NOI18N FileObject projectFO = project.getProjectDirectory(); try { GeneratedFilesHelper helper = new GeneratedFilesHelper(project.getProjectDirectory()); // Check if this is a codename one project FileObject cn1PropertiesFO = projectFO.getFileObject("codenameone_settings", "properties"); FileObject cn1LibraryPropertiesFO = projectFO.getFileObject("codenameone_library", "properties"); boolean isCodename1Lib = cn1LibraryPropertiesFO != null; boolean isCodename1Proj = cn1PropertiesFO != null; if ( isCodename1Lib || isCodename1Proj){ overrideTestTarget = false; } } catch ( Exception ex){} this.supportedActions = new HashMap<String, String>(); supportedActions.put(COMMAND_COMPILE_SINGLE, "compile-single"); // NOI18N supportedActions.put(COMMAND_TEST_SINGLE, "test-single"); // NOI18N supportedActions.put(COMMAND_DEBUG_TEST_SINGLE, "debug-test"); // NOI18N if ( overrideTestTarget ){ supportedActions.put(COMMAND_TEST, "test"); } // NOI18N addProjectSpecificActions(supportedActions); } /** * Implementors should create own map of the supported actions for a certain * type of project. The reason why this cannot be done in general is that we * have different ant target names in a different types of project (e.g. for * debugging single file there is a "debug-single-main" command in the Web * project, but "debug-single" command in the J2SE project) * * @return map where the key is command name and the value is a mirah target */ protected abstract void addProjectSpecificActions(Map<String, String> actionMap); @Override public String[] getSupportedActions() { FileObject destDirFO = project.getProjectDirectory().getFileObject("nbproject"); // NOI18N if (destDirFO != null) { FileObject mirahBuild = destDirFO.getFileObject("mirah-build.xml"); // NOI18N if (mirahBuild == null) { supportedActions.remove(COMMAND_TEST); } else { if (!supportedActions.containsKey(COMMAND_TEST)) { if ( overrideTestTarget ){ supportedActions.put(COMMAND_TEST, "test"); // NOI18N } } } } else { supportedActions.remove(COMMAND_TEST); } return supportedActions.keySet().toArray(new String[0]); } @Override public boolean isActionEnabled(String command, Lookup context) { if (supportedActions.keySet().contains(command)) { if (COMMAND_TEST.equals(command)) { return true; } FileObject[] testSources = findTestSources(context); FileObject[] sources = findSources(context); // Action invoked on file from "test" folder if (testSources != null) { return true; } // Action invoked on file from "src" folder if (sources != null && sources.length == 1) { if (COMMAND_TEST_SINGLE.equals(command) || COMMAND_DEBUG_TEST_SINGLE.equals(command)) { return false; } return true; } } return false; } @Override public void invokeAction(final String command, final Lookup context) { //System.out.println("Invoking action "+command); final Runnable action = new Runnable() { @Override public void run() { Properties p = new Properties(); String[] targetNames = getTargetNames(command, context, p); if (targetNames.length == 0) { targetNames = null; } if (p.keySet().isEmpty()) { p = null; } try { FileObject buildFo = findBuildXml(); if (buildFo == null || !buildFo.isValid()) { //The build.xml was deleted after the isActionEnabled was called NotifyDescriptor nd = new NotifyDescriptor.Message(NbBundle.getMessage(AbstractMirahActionProvider.class, "LBL_No_Build_XML_Found"), NotifyDescriptor.WARNING_MESSAGE); DialogDisplayer.getDefault().notify(nd); } else { ActionUtils.runTarget(buildFo, targetNames, p); } } catch (IOException e) { ErrorManager.getDefault().notify(e); } } }; if (supportedActions.containsKey(command)) { ScanDialog.runWhenScanFinished(action, NbBundle.getMessage(AbstractMirahActionProvider.class,"ACTION_"+command)); //NOI18N } else { action.run(); } } private String[] getTargetNames(String command, Lookup context, Properties p) { if (supportedActions.keySet().contains(command)) { if (command.equals(COMMAND_TEST)) { return setupTestAll(p); } FileObject[] testSources = findTestSources(context); if (testSources != null) { if (command.equals(COMMAND_RUN_SINGLE) || command.equals(COMMAND_TEST_SINGLE)) { return setupTestSingle(p, testSources); } else if (command.equals(COMMAND_DEBUG_SINGLE) || (command.equals(COMMAND_DEBUG_TEST_SINGLE))) { return setupDebugTestSingle(p, testSources); } else if (command.equals(COMMAND_COMPILE_SINGLE)) { return setupCompileSingle(p, testSources); } } else { FileObject file = findSources(context)[0]; Sources sources = ProjectUtils.getSources(project); SourceGroup[] sourceGroups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA); String clazz = FileUtil.getRelativePath(getRoot(sourceGroups, file), file); p.setProperty("javac.includes", clazz); // NOI18N // Convert foo/FooTest.java -> foo.FooTest if (clazz.endsWith(".mirah")) { // NOI18N clazz = clazz.substring(0, clazz.length() - 6); } clazz = clazz.replace('/','.'); String[] targets = loadTargetsFromConfig().get(command); if (command.equals(COMMAND_RUN_SINGLE)) { p.setProperty("run.class", clazz); // NOI18N } else if (command.equals(COMMAND_DEBUG_SINGLE)) { p.setProperty("debug.class", clazz); // NOI18N } else if (command.equals(COMMAND_COMPILE_SINGLE)) { p.setProperty("compile.class", clazz); // NOI18N } return getTargetNamesForCommand(targets, command); } } return new String[0]; } private String[] getTargetNamesForCommand(String[] targetsFromConfig, String commandName) { if (targetsFromConfig != null) { return targetsFromConfig; } else { return new String[] {supportedActions.get(commandName)}; } } private FileObject getRoot(SourceGroup[] groups, FileObject file) { assert file != null : "File can't be null"; //NOI18N FileObject srcDir = null; for (SourceGroup sourceGroup : groups) { FileObject root = sourceGroup.getRootFolder(); assert root != null : "Source Path Root can't be null"; //NOI18N if (FileUtil.isParentOf(root, file) || root.equals(file)) { srcDir = root; break; } } return srcDir; } private FileObject getRoot(FileObject[] groups, FileObject file) { assert file != null : "File can't be null"; //NOI18N FileObject srcDir = null; for (FileObject root : groups) { if (FileUtil.isParentOf(root, file) || root.equals(file)) { srcDir = root; break; } } return srcDir; } private FileObject[] findSources(Lookup context) { Sources sources = ProjectUtils.getSources(project); for (SourceGroup sourceGroup : sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA)) { FileObject[] files = ActionUtils.findSelectedFiles(context, sourceGroup.getRootFolder(), ".mirah", true); // NOI18N if (files != null) { return files; } } return null; } /** * Find either selected tests or tests which belong to selected source files */ private FileObject[] findTestSources(Lookup context) { for (FileObject testSourceRoot : getTestSourceRoots(project)) { FileObject[] files = ActionUtils.findSelectedFiles(context, testSourceRoot, ".mirah", true); // NOI18N if (files != null) { return files; } } return null; } private FileObject findBuildXml() { return getBuildXml(project); } public static String getBuildXmlName(final Project project) { assert project != null; String buildScriptPath = evaluateProperty(project, BUILD_SCRIPT); if (buildScriptPath == null) { buildScriptPath = GeneratedFilesHelper.BUILD_XML_PATH; } return buildScriptPath; } public static FileObject getBuildXml(final Project project) { return project.getProjectDirectory().getFileObject(getBuildXmlName(project)); } private static FileObject[] getTestSourceRoots(Project project) { List<String> names = getTestRootsNames(project); List<FileObject> result = new ArrayList<FileObject>(); for (String name : names) { // FileObject JavaDoc says that path delimited should be always '/' // See issue #238330 for more details FileObject root = project.getProjectDirectory().getFileObject(name.replace('\\', '/')); // NOI18N if (root != null) { result.add(root); } } return result.toArray(new FileObject[result.size()]); } private static List<String> getTestRootsNames(Project project) { List<String> result = new ArrayList<String>(); FileObject projectProperties = getPropertiesFO(project); if (projectProperties != null) { Map<String, String> map = getPropertiesMap(FileUtil.toFile(projectProperties)); for (Map.Entry<String, String> entry : map.entrySet()) { if (entry.getKey().startsWith("test.") && entry.getKey().endsWith(".dir")) { // NOI18N result.add(entry.getValue()); } } } return result; } private static String evaluateProperty(Project project, String key) { FileObject projectProperties = getPropertiesFO(project); if (projectProperties != null) { return getPropertiesMap(FileUtil.toFile(projectProperties)).get(key); } return null; } private static FileObject getPropertiesFO(Project project) { return project.getProjectDirectory().getFileObject("nbproject/project.properties"); // NOI18N } private static Map<String, String> getPropertiesMap(File projectPropertiesFO) { return PropertyUtils.propertiesFilePropertyProvider(projectPropertiesFO).getProperties(); } private String[] setupTestAll(Properties p) { // Convert foo/FooTest.java -> foo.FooTest System.out.println("Setting up testAll"); p.setProperty("test.binarytestincludes", "**/*Test.class"); // NOI18N p.setProperty("test.binaryexcludes", "**/*$*"); // NOI18N p.setProperty("test.binaryincludes", ""); // NOI18N System.out.println(p); return new String[] {"test-with-mirah"}; // NOI18N } private String[] setupTestSingle(Properties p, FileObject[] files) { FileObject[] testSrcPath = getTestSourceRoots(project); FileObject root = getRoot(testSrcPath, files[0]); String path = FileUtil.getRelativePath(root, files[0]); // Convert foo/FooTest.java -> foo.FooTest p.setProperty("test.binarytestincludes", path.substring(0, path.length() - 6) + ".class"); // NOI18N p.setProperty("test.binaryexcludes", "**/*$*"); // NOI18N p.setProperty("test.binaryincludes", ""); // NOI18N p.setProperty("javac.includes", ActionUtils.antIncludesList(files, root)); // NOI18N return new String[] {"test-single-mirah"}; // NOI18N } private String[] setupDebugTestSingle(Properties p, FileObject[] files) { FileObject[] testSrcPath = getTestSourceRoots(project); FileObject root = getRoot(testSrcPath, files[0]); String path = FileUtil.getRelativePath(root, files[0]); // Convert foo/FooTest.java -> foo.FooTest p.setProperty("test.binarytestincludes", path.substring(0, path.length() - 6) + ".class"); // NOI18N p.setProperty("test.binaryexcludes", "**/*$*"); // NOI18N p.setProperty("test.binaryincludes", ""); // NOI18N p.setProperty("test.class", path.substring(0, path.length() - 6).replace('/', '.')); // NOI18N p.setProperty("javac.includes", ActionUtils.antIncludesList(files, root)); // NOI18N return new String[] {"debug-test"}; // NOI18N } private String[] setupCompileSingle(Properties p, FileObject[] files) { FileObject[] testSrcPath = getTestSourceRoots(project); FileObject root = getRoot(testSrcPath, files[0]); String path = FileUtil.getRelativePath(root, files[0]); // Convert foo/FooTest.java -> foo.FooTest p.setProperty("compile.class", path.substring(0, path.length() - 6).replace('/', '.')); // NOI18N p.setProperty("javac.includes", ActionUtils.antIncludesList(files, root)); // NOI18N return new String[] {"compile-single"}; // NOI18N } // loads targets for specific commands from shared config property file // returns map; key=command name; value=array of targets for given command private HashMap<String, String[]> loadTargetsFromConfig() { HashMap<String, String[]> targets = new HashMap<String, String[]>(6); String config = evaluateProperty(project, PROP_CONFIG); // load targets from shared config FileObject propFO = project.getProjectDirectory().getFileObject("nbproject/configs/" + config + ".properties"); if (propFO == null) { return targets; } Properties props = new Properties(); try { InputStream is = propFO.getInputStream(); try { props.load(is); } finally { is.close(); } } catch (IOException ex) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex); return targets; } Enumeration propNames = props.propertyNames(); while (propNames.hasMoreElements()) { String propName = (String) propNames.nextElement(); if (propName.startsWith("$target.")) { String tNameVal = props.getProperty(propName); if (tNameVal != null && !tNameVal.equals("")) { String cmdNameKey = propName.substring("$target.".length()); StringTokenizer stok = new StringTokenizer(tNameVal.trim(), " "); List<String> targetNames = new ArrayList<String>(3); while (stok.hasMoreTokens()) { targetNames.add(stok.nextToken()); } targets.put(cmdNameKey, targetNames.toArray(new String[targetNames.size()])); } } } return targets; } }