/*
* 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.merbproject;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.netbeans.api.extexecution.ExecutionService;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
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.platform.execution.DirectoryFileLocator;
import org.netbeans.modules.ruby.platform.execution.RubyExecutionDescriptor;
import org.netbeans.modules.ruby.platform.execution.RubyProcessCreator;
import org.netbeans.modules.ruby.rubyproject.Util;
import org.netbeans.modules.ruby.rubyproject.ui.customizer.RubyProjectProperties;
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.Exceptions;
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 MerbProjectGenerator {
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 MerbProjectGenerator() {
}
/**
* 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 appType,
final RubyPlatform platform) throws IOException {
FileObject dirFO = null;
try {
dirFO = FileUtil.createFolder(dir);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
final String merbGen = platform.findExecutable("merb-gen");
String displayName = NbBundle.getMessage(MerbProjectGenerator.class, "GenerateMerb");
File pwd = dir.getParentFile();
RubyExecutionDescriptor desc = new RubyExecutionDescriptor(platform, displayName, pwd, merbGen);
desc.additionalArgs("app", prjName, appType);
desc.fileLocator(new DirectoryFileLocator(dirFO));
// LineConvertor convertor = LineConvertors.filePattern(locator, RAILS_GENERATOR_PATTERN, RubyLineConvertorFactory.EXT_RE, 2, -1);
desc.addStandardRecognizers();
// desc.addErrConvertor(convertor);
// desc.addOutConvertor(convertor);
RubyProcessCreator rpc = new RubyProcessCreator(desc);
ExecutionService es = ExecutionService.newService(rpc, desc.toExecutionDescriptor(), displayName);
try {
es.run().get();
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
} catch (ExecutionException ex) {
Exceptions.printStackTrace(ex);
}
RakeProjectHelper helper = createBasicProjectMetadata(dirFO, prjName,
"app", null, "spec", null, platform); // NOI18N
Project project = ProjectManager.getDefault().findProject(dirFO);
ProjectManager.getDefault().saveProject(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 MerbProject project = (MerbProject) 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);
}
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 MerbProject 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(MerbProjectType.PROJECT_CONFIGURATION_NAMESPACE, "source-roots"); // NOI18N
assert nl.getLength() == 1;
Element sourceRoots = (Element) nl.item(0);
nl = data.getElementsByTagNameNS(MerbProjectType.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], MerbProject.SOURCES_TYPE_RUBY);
Element root = doc.createElementNS(MerbProjectType.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], MerbProject.SOURCES_TYPE_RUBY);
Element root = doc.createElementNS(MerbProjectType.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, MerbProjectType.TYPE);
Element data = helper.getPrimaryConfigurationData(true);
Document doc = data.getOwnerDocument();
Element nameEl = doc.createElementNS(MerbProjectType.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(MerbProjectType.PROJECT_CONFIGURATION_NAMESPACE, "source-roots"); // NOI18N
if (srcRoot != null) {
Element root = doc.createElementNS(MerbProjectType.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(MerbProjectType.PROJECT_CONFIGURATION_NAMESPACE, "test-roots"); // NOI18N
if (testRoot != null) {
Element root = doc.createElementNS(MerbProjectType.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(MerbProjectType.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(MerbProjectGenerator.class, "USG_PROJECT_CREATE_MERB", // 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(MerbProjectGenerator.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();
}
}