/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2010 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]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun * Microsystems, Inc. All Rights Reserved. * * 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. */ package org.netbeans.modules.ruby.rubyproject; import java.io.File; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectManager; import org.netbeans.modules.ruby.rubyproject.ui.customizer.RubyProjectProperties; import org.netbeans.modules.ruby.spi.project.support.rake.RakeProjectHelper; import org.netbeans.modules.ruby.spi.project.support.rake.ProjectGenerator; import org.netbeans.api.queries.FileEncodingQuery; import org.netbeans.api.ruby.platform.RubyPlatform; import org.netbeans.modules.ruby.rubyproject.rake.RakeSupport; import org.netbeans.modules.ruby.spi.project.support.rake.ReferenceHelper; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.loaders.DataFolder; import org.openide.loaders.DataObject; import org.openide.util.Mutex; import org.openide.util.MutexException; import org.openide.ErrorManager; import org.openide.filesystems.FileAlreadyLockedException; import org.openide.util.EditableProperties; import org.openide.util.NbBundle; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; /** * Able to create NetBeans Ruby project either from scratch or from existing * sources. */ public final class RubyProjectGenerator { public static final String DEFAULT_SRC_NAME = "src.dir"; // NOI18N public static final String DEFAULT_TEST_SRC_NAME = "test.src.dir"; // NOI18N public static final String DEFAULT_SPEC_SRC_NAME = "spec.src.dir"; // NOI18N private RubyProjectGenerator() {} /** * Create a new empty NetBeans Ruby project. * * @param dir the top-level directory (need not yet exist but if it does it must be empty) * @param prjName the name for the project * @param mainClass might be <tt>null</tt> to skip mainclass generation * @param platform project's platform * @return the helper object permitting it to be further customized * @throws IOException in case something went wrong */ public static RakeProjectHelper createProject(File dir, String prjName, String mainClass, final RubyPlatform platform) throws IOException { FileObject dirFO = FileUtil.createFolder(dir); RakeProjectHelper helper = createBasicProjectMetadata(dirFO, prjName, "lib", "test", "spec", mainClass, platform); // NOI18N Project project = ProjectManager.getDefault().findProject(dirFO); ProjectManager.getDefault().saveProject(project); FileObject srcFolder = dirFO.createFolder("lib"); // NOI18N dirFO.createFolder("test"); // NOI18N dirFO.createFolder("spec"); // NOI18N if (mainClass != null) { createFromTemplate(mainClass, srcFolder, "Templates/Ruby/main.rb"); // NOI18N } // Rakefile final Map<String, String> rakeProps = new HashMap<String, String>(); rakeProps.put("PROJECT_NAME", dir.getName()); // NOI18N if (RSpecSupport.hasRSpecInstalled(platform)) { rakeProps.put("REQUIRE_SPEC_TASK", "require 'rspec/core/rake_task'\n"); // NOI18N // add source root as libs for rspec StringBuilder dirLibs = new StringBuilder(); RubyBaseProject baseProject = project.getLookup().lookup(RubyBaseProject.class); FileObject[] roots = baseProject.getSourceRootFiles(); for (int i = 0; i < roots.length; i++) { dirLibs.append("\""); dirLibs.append(roots[i].getName()); dirLibs.append("\""); if (i + 1 < roots.length) { dirLibs.append(","); } } String specTaskDef = "\nRSpec::Core::RakeTask.new do |spec|\n" + // NOI18N " spec.pattern = 'spec/erector/*_spec.rb'\n" + // NOI18N " spec.rspec_opts = [Dir[" + dirLibs.toString() + "].to_a.join(':')]\n" + // NOI18N "end"; // NOI18N rakeProps.put("SPEC_TASK_DEF", specTaskDef); // NOI18N } else { rakeProps.put("REQUIRE_SPEC_TASK", ""); // NOI18N rakeProps.put("SPEC_TASK_DEF", ""); // NOI18N } createFromTemplate("Rakefile", dirFO, "Templates/Ruby/Rakefile", rakeProps); // NOI18N createFileWithContent(dirFO, "README", "TXT_README_Content", prjName); // NOI18N createFileWithContent(dirFO, "LICENSE", "TXT_LICENSE_Content", prjName); // NOI18N RakeSupport.refreshTasks(project); return helper; } /** * Creates a new empty Ruby project with initially set up source and test * folder. Used for creating NetBeans Ruby project from existing sources. * * @param dir the top-level directory (need not yet exist but if it does it must be empty) * @param prjName the name for the project * @param sourceFolders initial source folders * @param testFolders initial test folders * @param platform project's platform * @return the helper object permitting it to be further customized * @throws IOException in case something went wrong */ public static RakeProjectHelper createProject(final File dir, final String prjName, final File[] sourceFolders, final File[] testFolders, final RubyPlatform platform) throws IOException { assert sourceFolders != null && testFolders != null: "Package roots can't be null"; // NOI18N final FileObject dirFO = FileUtil.createFolder(dir); final RakeProjectHelper helper = createBasicProjectMetadata(dirFO, prjName, null, null, null, null, platform); final RubyProject project = (RubyProject) ProjectManager.getDefault().findProject(dirFO); try { ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void>() { public Void run() throws IOException { createProjectMetadata(project, helper, sourceFolders, testFolders, prjName); return null; } }); } catch (MutexException me) { ErrorManager.getDefault().notify(me); } RakeSupport.refreshTasks(project); return helper; } /** * Sets up nbproject folder appropriately according to the given data. That * is project.xml. That is project.properties, project.xml, etc. * * @param project project for which to create metadata * @param helper {@link RakeProjectHelper} * @param sourceFolders initial source folders * @param testFolders initial test folders * @param prjName the name for the project * @throws IOException in case something went wrong */ private static void createProjectMetadata(final RubyProject project, final RakeProjectHelper helper, final File[] sourceFolders, final File[] testFolders, final String prjName) throws IOException { ReferenceHelper refHelper = project.getReferenceHelper(); Element data = helper.getPrimaryConfigurationData(true); Document doc = data.getOwnerDocument(); NodeList nl = data.getElementsByTagNameNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE, "source-roots"); // NOI18N assert nl.getLength() == 1; Element sourceRoots = (Element) nl.item(0); nl = data.getElementsByTagNameNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE, "test-roots"); // NOI18N assert nl.getLength() == 1; Element testRoots = (Element) nl.item(0); for (int i = 0; i < sourceFolders.length; i++) { String propName; if (i == 0) { propName = "src.dir"; // NOI18N } else { String name = sourceFolders[i].getName(); propName = name + ".dir"; // NOI18N } int rootIndex = 1; EditableProperties props = helper.getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH); while (props.containsKey(propName)) { rootIndex++; propName = prjName + rootIndex + ".dir"; // NOI18N } String srcReference = refHelper.createForeignFileReference(sourceFolders[i], RubyProject.SOURCES_TYPE_RUBY); Element root = doc.createElementNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE, "root"); // NOI18N root.setAttribute("id", propName); // NOI18N sourceRoots.appendChild(root); props = helper.getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH); props.put(propName, srcReference); helper.putProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH, props); // #47609 } for (int i = 0; i < testFolders.length; i++) { if (!testFolders[i].exists()) { testFolders[i].mkdirs(); } String propName; if (i == 0) { propName = "test.src.dir"; // NOI18N } else { String name = testFolders[i].getName(); propName = "test." + name + ".dir"; // NOI18N } int rootIndex = 1; EditableProperties props = helper.getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH); while (props.containsKey(propName)) { rootIndex++; propName = "test." + prjName + rootIndex + ".dir"; // NOI18N } String testReference = refHelper.createForeignFileReference(testFolders[i], RubyProject.SOURCES_TYPE_RUBY); Element root = doc.createElementNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE, "root"); // NOI18N root.setAttribute("id", propName); // NOI18N testRoots.appendChild(root); props = helper.getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH); // #47609 props.put(propName, testReference); helper.putProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH, props); } helper.putPrimaryConfigurationData(data, true); ProjectManager.getDefault().saveProject(project); } /** * Creates very basic project skeleton. */ private static RakeProjectHelper createBasicProjectMetadata(FileObject dirFO, String name, String srcRoot, String testRoot, String specRoot, String mainClass, final RubyPlatform platform) throws IOException { RakeProjectHelper helper = ProjectGenerator.createProject(dirFO, RubyProjectType.TYPE); Element data = helper.getPrimaryConfigurationData(true); Document doc = data.getOwnerDocument(); Element nameEl = doc.createElementNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE, "name"); // NOI18N nameEl.appendChild(doc.createTextNode(name)); data.appendChild(nameEl); EditableProperties ep = helper.getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH); Element sourceRoots = doc.createElementNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE,"source-roots"); // NOI18N if (srcRoot != null) { Element root = doc.createElementNS (RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE,"root"); // NOI18N root.setAttribute ("id", "src.dir"); // NOI18N sourceRoots.appendChild(root); ep.setProperty("src.dir", srcRoot); // NOI18N } data.appendChild (sourceRoots); Element testRoots = doc.createElementNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE, "test-roots"); // NOI18N if (testRoot != null) { Element root = doc.createElementNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE, "root"); // NOI18N root.setAttribute("id", "test.src.dir"); // NOI18N testRoots.appendChild(root); ep.setProperty("test.src.dir", testRoot); // NOI18N } if (specRoot != null) { Element root = doc.createElementNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE, "root"); // NOI18N root.setAttribute("id", "spec.src.dir"); // NOI18N testRoots.appendChild(root); ep.setProperty("spec.src.dir", specRoot); // NOI18N } data.appendChild(testRoots); helper.putPrimaryConfigurationData(data, true); Charset enc = FileEncodingQuery.getDefaultEncoding(); ep.setProperty(RubyProjectProperties.SOURCE_ENCODING, enc.name()); ep.setProperty(RubyProjectProperties.MAIN_CLASS, mainClass == null ? "" : mainClass); // NOI18N RubyProjectProperties.storePlatform(ep, platform); helper.putProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH, ep); Util.logUsage(RubyProjectGenerator.class, "USG_PROJECT_CREATE_RUBY", // NOI18N platform.getInfo().getKind(), platform.getInfo().getPlatformVersion(), platform.getInfo().getGemVersion()); return helper; } private static DataObject createFromTemplate(String mainClassName, FileObject srcFolder, String templateName) throws IOException { return createFromTemplate(mainClassName, srcFolder, templateName, null); } private static DataObject createFromTemplate(String mainClassName, FileObject srcFolder, String templateName, final Map<String, ? extends Object> props) throws IOException { int lastDotIdx = mainClassName.lastIndexOf('/'); String mName, pName; if ( lastDotIdx == -1 ) { mName = mainClassName.trim(); pName = null; } else { mName = mainClassName.substring( lastDotIdx + 1 ).trim(); pName = mainClassName.substring( 0, lastDotIdx ).trim(); } if ( mName.length() == 0 ) { return null; } FileObject mainTemplate = FileUtil.getConfigFile( templateName ); if ( mainTemplate == null ) { return null; // Don't know the template } DataObject mt = DataObject.find( mainTemplate ); FileObject pkgFolder = srcFolder; if ( pName != null ) { String fName = pName.replace( '.', '/' ); pkgFolder = FileUtil.createFolder( srcFolder, fName ); } DataFolder pDf = DataFolder.findFolder( pkgFolder ); mName = Util.stripExtension(mName, ".rb"); // NOI18N if (props != null) { return mt.createFromTemplate(pDf, mName, props); } else { return mt.createFromTemplate(pDf, mName); } } private static void createFileWithContent(final FileObject dirFO, final String fileName, final String contentKey, final String prjName) throws IOException { FileObject newFile = dirFO.createData(fileName); // NOI18N writeLines(newFile, NbBundle.getMessage(RubyProjectGenerator.class, contentKey, prjName)); } // TODO: use FileUtils when #118087 is fixed private static void writeLines(final FileObject readme, final String... lines) throws FileAlreadyLockedException, IOException { PrintWriter readmeW = new PrintWriter(new OutputStreamWriter(readme.getOutputStream(), "UTF-8")); // NOI18N for (String line : lines) { readmeW.println(line); } readmeW.close(); } }