/*
* 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):
*
* Portions Copyrighted 2008 Sun Microsystems, Inc.
*/
package org.netbeans.modules.ruby.rubyproject;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.ListCellRenderer;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.api.queries.FileEncodingQuery;
import org.netbeans.api.ruby.platform.RubyPlatform;
import org.netbeans.modules.ruby.rubyproject.ProjectPropertyExtender.Item;
import org.netbeans.modules.ruby.spi.project.support.rake.GeneratedFilesHelper;
import org.netbeans.modules.ruby.spi.project.support.rake.PropertyEvaluator;
import org.netbeans.modules.ruby.spi.project.support.rake.RakeProjectHelper;
import org.netbeans.modules.ruby.spi.project.support.rake.ReferenceHelper;
import org.netbeans.modules.ruby.spi.project.support.rake.ui.StoreGroup;
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.EditableProperties;
import org.openide.util.Mutex;
import org.openide.util.MutexException;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
public abstract class SharedRubyProjectProperties {
private static final Logger LOGGER = Logger.getLogger(SharedRubyProjectProperties.class.getName());
public static final String MAIN_CLASS = "main.file"; // NOI18N
public static final String RUBY_OPTIONS = "ruby.options"; // NOI18N
public static final String DIST_DIR = "dist.dir"; // NOI18N
public static final String BUILD_DIR = "build.dir"; // NOI18N
public static final String PLATFORM_ACTIVE = "platform.active"; // NOI18N
public static final String JAVAC_CLASSPATH = "javac.classpath"; // NOI18N
public static final String RAKE_ARGS = "rake.args"; // NOI18N
// if you change the value, update also o.n.m.ruby.RubyParser
public static final String JVM_ARGS = "jvm.args"; // NOI18N
public static final String SOURCE_ENCODING="source.encoding"; // NOI18N
public static final String APPLICATION_ARGS = "application.args"; // NOI18N
/**
* The project property for the RAILS_ENV env variable; defined here instead
* of RailsProjectProperties as this is needed in RakeRunner.
*/
public static final String RAILS_ENV = "rails.env"; // NOI18N
/**
* Support for odd property name ('run.jvmargs'). Will be dropped in the
* future
*/
public static final String RUBY_OPTIONS_DEPRECATED = "run.jvmargs"; // NOI18N
/**
* The key for the names of the rake tasks that invoke RSpec tests and should be
* run using the UI test runner.
*/
public static final String SPEC_TASKS = "spec.tasks"; //NOI18N
/**
* The key for the names of the rake tasks that invoke Test/Unit tests and should be
* run using the UI test runner.
*/
public static final String TEST_TASKS = "test.tasks"; //NOI18N
// External Java integration
public DefaultListModel JAVAC_CLASSPATH_MODEL;
//public ButtonModel INCLUDE_JAVA_MODEL;
public ListCellRenderer CLASS_PATH_LIST_RENDERER;
private final RubyBaseProject project;
private RubyPlatform platform;
protected final PropertyEvaluator evaluator;
private final UpdateHelper updateHelper;
private final GeneratedFilesHelper genFileHelper;
private final ProjectPropertyExtender cs;
private final StoreGroup privateGroup;
private final StoreGroup projectGroup;
private final Map<String, String> additionalProperties;
private String activeConfig;
private Map<String, Map<String, String>> runConfigs;
private List<GemRequirement> gemRequirements;
private List<GemRequirement> gemRequirementsTests;
public static final String[] WELL_KNOWN_PATHS = new String[]{
"${" + JAVAC_CLASSPATH + "}", // NOI18N
};
public static final String LIBRARY_PREFIX = "${libs."; // NOI18N
public static final String LIBRARY_SUFFIX = ".classpath}"; // NOI18N
// XXX looks like there is some kind of API missing in ReferenceHelper?
public static final String ANT_ARTIFACT_PREFIX = "${reference."; // NOI18N
//public abstract DefaultListModel getListModel(String propertyName);
//public abstract ListCellRenderer getListRenderer(String propertyName);
public SharedRubyProjectProperties(
final RubyBaseProject project,
final PropertyEvaluator evaluator,
final UpdateHelper updateHelper,
final GeneratedFilesHelper genFileHelper,
final ReferenceHelper refHelper) {
this.project = project;
this.updateHelper = updateHelper;
this.genFileHelper = genFileHelper;
this.evaluator = evaluator;
this.cs = new ProjectPropertyExtender(evaluator, refHelper, updateHelper.getRakeProjectHelper(),
WELL_KNOWN_PATHS, LIBRARY_PREFIX, LIBRARY_SUFFIX, ANT_ARTIFACT_PREFIX);
additionalProperties = new HashMap<String, String>();
privateGroup = new StoreGroup();
projectGroup = new StoreGroup();
init();
}
protected abstract String[] getConfigProperties();
protected abstract String[] getConfigPrivateProperties();
protected abstract void prePropertiesStore() throws IOException;
protected abstract void storeProperties(
final EditableProperties projectProperties,
final EditableProperties privateProperties) throws IOException;
/** Initializes the visual models/ */
private void init() {
CLASS_PATH_LIST_RENDERER = new JavaClassPathUi.ClassPathListCellRenderer(evaluator);
EditableProperties projectProperties = getUpdateHelper().getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH);
String cp = projectProperties.get(JAVAC_CLASSPATH);
JAVAC_CLASSPATH_MODEL = /*ClassPathUiSupport.*/ createListModel(cs.itemsIterator(cp));
// CustomizerRun
runConfigs = readRunConfigs();
activeConfig = evaluator.getProperty("config"); // NOI18N
}
protected UpdateHelper getUpdateHelper() {
return updateHelper;
}
protected RubyBaseProject getProject() {
return project;
}
public RubyPlatform getPlatform() {
return getProject().getPlatform();
}
public static String getRubyOptions(final RubyBaseProject project) {
String options = project.evaluator().getProperty(SharedRubyProjectProperties.RUBY_OPTIONS);
if (isEmpty(options)) {
options = null;
}
if (options == null) { // support for depreacted 'run.jvmargs' key
options = project.evaluator().getProperty(SharedRubyProjectProperties.RUBY_OPTIONS_DEPRECATED);
if (isEmpty(options)) {
options = null;
}
}
return options;
}
public void setPlatform(final RubyPlatform platform) {
this.platform = platform;
}
public static void storePlatform(final EditableProperties ep, final RubyPlatform platform) {
if (platform == null) {
LOGGER.fine("Project has invalid platform (null).");
return;
}
ep.setProperty(PLATFORM_ACTIVE, platform.getID());
}
public String getActiveConfig() {
return activeConfig;
}
public void setActiveConfig(String activeConfig) {
this.activeConfig = activeConfig;
}
public List<GemRequirement> getGemRequirements() {
return gemRequirements;
}
public void setGemRequirements(List<GemRequirement> gemRequirements) {
this.gemRequirements = gemRequirements;
}
public List<GemRequirement> getGemRequirementsForTests() {
return gemRequirementsTests;
}
public void setGemRequirementsForTests(List<GemRequirement> gemRequirements) {
this.gemRequirementsTests = gemRequirements;
}
public Map<String, Map<String, String>> getRunConfigs() {
return runConfigs;
}
private boolean isPrivateConfigProperty(final String prop) {
return Arrays.asList(getConfigPrivateProperties()).contains(prop);
}
protected Map<String, Map<String, String>> readRunConfigs() {
Map<String, Map<String, String>> m = new TreeMap<String, Map<String, String>>(new Comparator<String>() {
public int compare(String s1, String s2) {
return s1 != null ? (s2 != null ? s1.compareTo(s2) : 1) : (s2 != null ? -1 : 0);
}
});
Map<String, String> def = new TreeMap<String, String>();
for (String prop : getConfigProperties()) {
String v = getUpdateHelper().getProperties(RakeProjectHelper.PRIVATE_PROPERTIES_PATH).getProperty(prop);
if (v == null) {
v = getUpdateHelper().getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH).getProperty(prop);
}
if (v != null) {
def.put(prop, v);
}
}
m.put(null, def);
FileObject configs = project.getProjectDirectory().getFileObject("nbproject/configs"); // NOI18N
if (configs != null) {
for (FileObject kid : configs.getChildren()) {
if (!kid.hasExt("properties")) { // NOI18N
continue;
}
m.put(kid.getName(), new TreeMap<String, String>(getUpdateHelper().getProperties(FileUtil.getRelativePath(project.getProjectDirectory(), kid))));
}
}
configs = project.getProjectDirectory().getFileObject("nbproject/private/configs"); // NOI18N
if (configs != null) {
for (FileObject kid : configs.getChildren()) {
if (!kid.hasExt("properties")) { // NOI18N
continue;
}
Map<String, String> c = m.get(kid.getName());
if (c == null) {
continue;
}
c.putAll(new HashMap<String, String>(getUpdateHelper().getProperties(FileUtil.getRelativePath(project.getProjectDirectory(), kid))));
}
}
return m;
}
protected void storeRunConfigs(Map<String, Map<String, String>> configs,
EditableProperties projectProperties, EditableProperties privateProperties) throws IOException {
Map<String, String> defaultConf = configs.get(null);
for (String confProp : getConfigProperties()) {
String defConfValue = defaultConf.get(confProp);
EditableProperties ep = isPrivateConfigProperty(confProp) ? privateProperties : projectProperties;
if (!Utilities.compareObjects(defConfValue, ep.getProperty(confProp))) {
if (defConfValue != null && defConfValue.length() > 0) {
ep.setProperty(confProp, defConfValue);
} else {
ep.remove(confProp);
}
}
}
for (Map.Entry<String, Map<String, String>> entry : configs.entrySet()) {
String config = entry.getKey();
if (config == null) { // default one
continue;
}
String sharedPath = "nbproject/configs/" + config + ".properties"; // NOI18N
String privatePath = "nbproject/private/configs/" + config + ".properties"; // NOI18N
Map<String, String> c = entry.getValue();
if (c == null) {
updateHelper.putProperties(sharedPath, null);
updateHelper.putProperties(privatePath, null);
continue;
}
for (Map.Entry<String, String> entry2 : c.entrySet()) {
String prop = entry2.getKey();
String v = entry2.getValue();
String path = isPrivateConfigProperty(prop) ? privatePath : sharedPath;
EditableProperties ep = updateHelper.getProperties(path);
if (!Utilities.compareObjects(v, ep.getProperty(prop))) {
if (v != null && (v.length() > 0 || (defaultConf.get(prop) != null && defaultConf.get(prop).length() > 0))) {
ep.setProperty(prop, v);
} else {
ep.remove(prop);
}
updateHelper.putProperties(path, ep);
}
}
// Make sure the definition file is always created, even if it is empty.
updateHelper.putProperties(sharedPath, updateHelper.getProperties(sharedPath));
}
}
public void putAdditionalProperty(String propertyName, String propertyValue) {
additionalProperties.put(propertyName, propertyValue);
}
protected static boolean showModifiedMessage(String title) {
String message = NbBundle.getMessage(SharedRubyProjectProperties.class, "TXT_Regenerate");
JButton regenerateButton = new JButton(NbBundle.getMessage(SharedRubyProjectProperties.class, "CTL_RegenerateButton"));
regenerateButton.setDefaultCapable(true);
regenerateButton.getAccessibleContext().setAccessibleDescription(
NbBundle.getMessage(SharedRubyProjectProperties.class, "AD_RegenerateButton"));
NotifyDescriptor d = new NotifyDescriptor.Message(message, NotifyDescriptor.WARNING_MESSAGE);
d.setTitle(title);
d.setOptionType(NotifyDescriptor.OK_CANCEL_OPTION);
d.setOptions(new Object[]{regenerateButton, NotifyDescriptor.CANCEL_OPTION});
return DialogDisplayer.getDefault().notify(d) == regenerateButton;
}
private void storeCommonProperties() throws IOException {
// Encode all paths (this may change the project properties)
String[] javac_cp = cs.encodeToStrings(/*ClassPathUiSupport.*/getIterator(JAVAC_CLASSPATH_MODEL));
prePropertiesStore();
// Store standard properties
EditableProperties projectProperties = getUpdateHelper().getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH);
EditableProperties privateProperties = getUpdateHelper().getProperties(RakeProjectHelper.PRIVATE_PROPERTIES_PATH);
storeProperties(projectProperties, privateProperties);
SharedRubyProjectProperties.storePlatform(privateProperties, getPlatform());
// Standard store of the properties
projectGroup.store(projectProperties);
privateGroup.store(privateProperties);
storeRunConfigs(runConfigs, projectProperties, privateProperties);
EditableProperties configProperties = getUpdateHelper().getProperties("nbproject/private/config.properties"); // NOI18N
if (activeConfig == null) {
configProperties.remove("config"); // NOI18N
} else {
configProperties.setProperty("config", activeConfig); // NOI18N
}
getUpdateHelper().putProperties("nbproject/private/config.properties", configProperties); // NOI18N
putGemRequirements(projectProperties);
// Save all paths
projectProperties.setProperty(JAVAC_CLASSPATH, javac_cp);
projectProperties.putAll(additionalProperties);
// Store the property changes into the project
getUpdateHelper().putProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH, projectProperties);
getUpdateHelper().putProperties(RakeProjectHelper.PRIVATE_PROPERTIES_PATH, privateProperties);
// Ugh - this looks like global clobbering!
String encoding = additionalProperties.get(SOURCE_ENCODING);
if (encoding != null) {
try {
FileEncodingQuery.setDefaultEncoding(Charset.forName(encoding));
} catch (UnsupportedCharsetException e) {
//When the encoding is not supported by JVM do not set it as default
}
}
}
private void putGemRequirements(EditableProperties projectProperties) {
if (gemRequirements == null) {
projectProperties.remove(RequiredGems.REQUIRED_GEMS_PROPERTY);
} else {
projectProperties.put(RequiredGems.REQUIRED_GEMS_PROPERTY, RequiredGems.asString(gemRequirements));
}
if (gemRequirementsTests == null) {
projectProperties.remove(RequiredGems.REQUIRED_GEMS_TESTS_PROPERTY);
} else {
projectProperties.put(RequiredGems.REQUIRED_GEMS_TESTS_PROPERTY, RequiredGems.asString(gemRequirementsTests));
}
}
public void save() {
try {
// Store properties
Boolean result = ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Boolean>() {
final FileObject projectDir = getUpdateHelper().getRakeProjectHelper().getProjectDirectory();
public Boolean run() throws IOException {
if ((genFileHelper.getBuildScriptState(GeneratedFilesHelper.BUILD_IMPL_XML_PATH,
project.getClass().getResource("resources/build-impl.xsl")) //NOI18N
& GeneratedFilesHelper.FLAG_MODIFIED) == GeneratedFilesHelper.FLAG_MODIFIED) { //NOI18N
if (showModifiedMessage(NbBundle.getMessage(SharedRubyProjectProperties.class, "TXT_ModifiedTitle"))) {
//Delete user modified build-impl.xml
FileObject fo = projectDir.getFileObject(GeneratedFilesHelper.BUILD_IMPL_XML_PATH);
if (fo != null) {
fo.delete();
}
} else {
return false;
}
}
storeCommonProperties();
return true;
}
});
// and save the project
if (result == Boolean.TRUE) {
ProjectManager.getDefault().saveProject(getProject());
}
} catch (MutexException e) {
ErrorManager.getDefault().notify((IOException) e.getException());
} catch (IOException ex) {
ErrorManager.getDefault().notify(ex);
}
}
// From ClassPathUiSupport:
private static DefaultListModel createListModel(final Iterator it) {
DefaultListModel model = new DefaultListModel();
while (it.hasNext()) {
model.addElement(it.next());
}
return model;
}
// From ClassPathUiSupport:
private static Iterator<Item> getIterator(final DefaultListModel model) {
// XXX Better performing impl. would be nice
return getList(model).iterator();
}
// From ClassPathUiSupport:
private static List<Item> getList(final DefaultListModel model) {
@SuppressWarnings("unchecked")
List<Item> items = (List<Item>) Collections.list(model.elements());
return items;
}
/** Tests whether the given string is non-null and empty. */
private static boolean isEmpty(final String options) {
return options != null && options.trim().length() == 0;
}
}