/*
* 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.IOException;
import javax.swing.JButton;
import org.w3c.dom.Element;
import org.openide.DialogDisplayer;
import org.openide.ErrorManager;
import org.openide.NotifyDescriptor;
import org.openide.util.NbBundle;
import org.openide.util.Mutex;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.spi.project.AuxiliaryConfiguration;
import org.netbeans.modules.ruby.spi.project.support.rake.RakeProjectHelper;
import org.netbeans.modules.ruby.spi.project.support.rake.GeneratedFilesHelper;
import org.openide.util.EditableProperties;
/**
* (I probably don't need this yet)
*
* Proxy for the RakeProjectHelper which defers the update of the project metadata
* to explicit user action. Currently it is hard coded for update from
* "http://www.netbeans.org/ns/j2se-project/1" to "http://www.netbeans.org/ns/j2se-project/2".
* In future it should define plugable SPI.
*/
public class UpdateHelper {
private static final boolean TRANSPARENT_UPDATE = Boolean.getBoolean("j2seproject.transparentUpdate"); // NOI18N
private static final String BUILD_NUMBER = System.getProperty("netbeans.buildnumber"); // NOI18N
//private static final String MINIMUM_ANT_VERSION_ELEMENT = "minimum-ant-version";
private final Project project;
private final RakeProjectHelper helper;
private final AuxiliaryConfiguration cfg;
private final GeneratedFilesHelper genFileHelper;
private final Notifier notifier;
private boolean alreadyAskedInWriteAccess;
private Boolean isCurrent;
private Element cachedElement;
private final String projectConfigurationNamespace;
/**
* Creates new UpdateHelper
* @param project
* @param helper RakeProjectHelper
* @param cfg AuxiliaryConfiguration
* @param genFileHelper GeneratedFilesHelper
* @param notifier used to ask user about project update
*/
UpdateHelper(Project project, RakeProjectHelper helper, AuxiliaryConfiguration cfg,
GeneratedFilesHelper genFileHelper, Notifier notifier, String projectConfigurationNamespace) {
assert project != null && helper != null && cfg != null && genFileHelper != null && notifier != null;
this.project = project;
this.helper = helper;
this.cfg = cfg;
this.genFileHelper = genFileHelper;
this.notifier = notifier;
this.projectConfigurationNamespace = projectConfigurationNamespace;
}
/**
* Returns the RakeProjectHelper.getProperties(), {@link RakeProjectHelper#getProperties(String)}
* @param path a relative URI in the project directory.
* @return a set of properties
*/
public EditableProperties getProperties (final String path) {
//Properties are the same in both j2seproject/1 and j2seproject/2
return ProjectManager.mutex().readAccess(new Mutex.Action<EditableProperties>(){
public EditableProperties run() {
if (!isCurrent() && RakeProjectHelper.PROJECT_PROPERTIES_PATH.equals(path)) { //Only project properties were changed
return getUpdatedProjectProperties ();
}
else {
return helper.getProperties(path);
}
}
});
}
/**
* In the case that the project is of current version or the properties are not {@link RakeProjectHelper#PROJECT_PROPERTIES_PATH}
* it calls RakeProjectHelper.putProperties(), {@link RakeProjectHelper#putProperties(String, EditableProperties)}
* otherwise it asks user to updata project. If the user agrees with the project update, it does the update and calls
* RakeProjectHelper.putProperties().
* @param path a relative URI in the project directory.
* @param props a set of properties
*/
public void putProperties (final String path, final EditableProperties props) {
ProjectManager.mutex().writeAccess(
new Runnable () {
public void run() {
if (isCurrent() || !RakeProjectHelper.PROJECT_PROPERTIES_PATH.equals(path)) { //Only project props should cause update
helper.putProperties(path,props);
}
else if (canUpdate()) {
try {
saveUpdate ();
helper.putProperties(path,props);
} catch (IOException ioe) {
ErrorManager.getDefault().notify (ioe);
}
}
}
});
}
/**
* In the case that the project is of current version or shared is false it delegates to
* RakeProjectHelper.getPrimaryConfigurationData(), {@link RakeProjectHelper#getPrimaryConfigurationData(boolean)}.
* Otherwise it creates an in memory update of shared configuration data and returns it.
* @param shared if true, refers to <code>project.xml</code>, else refers to
* <code>private.xml</code>
* @return the configuration data that is available
*/
public Element getPrimaryConfigurationData (final boolean shared) {
return ProjectManager.mutex().readAccess(new Mutex.Action<Element>(){
public Element run() {
if (!shared || isCurrent()) { //Only shared props should cause update
return helper.getPrimaryConfigurationData(shared);
}
else {
return getUpdatedSharedConfigurationData ();
}
}
});
}
/**
* In the case that the project is of current version or shared is false it calls RakeProjectHelper.putPrimaryConfigurationData(),
* {@link RakeProjectHelper#putPrimaryConfigurationData(Element, boolean)}.
* Otherwise it asks user to update the project. If the user agrees with the project update, it does the update and calls
* RakeProjectHelper.PrimaryConfigurationData().
* @param element the configuration data
* @param shared if true, refers to <code>project.xml</code>, else refers to
* <code>private.xml</code>
*/
public void putPrimaryConfigurationData (final Element element, final boolean shared) {
ProjectManager.mutex().writeAccess(new Runnable () {
public void run () {
if (!shared || isCurrent()) {
helper.putPrimaryConfigurationData(element, shared);
} else if (canUpdate()) {
try {
saveUpdate ();
helper.putPrimaryConfigurationData(element, shared);
} catch (IOException ioe) {
ErrorManager.getDefault().notify(ioe);
}
}
}
});
}
/**
* Returns an RakeProjectHelper. The helper may not be used for accessing/storing project metadata.
* For project metadata manipulation the UpdateHelper must be used.
* @return RakeProjectHelper
*/
public RakeProjectHelper getRakeProjectHelper () {
return this.helper;
}
/**
* Request an saving of update. If the project is not of current version the user will be asked to update the project.
* If the user agrees with an update the project is updated.
* @return true if the metadata are of current version or updated
*/
public boolean requestSave () throws IOException{
if (isCurrent()) {
return true;
}
if (!canUpdate()) {
return false;
}
saveUpdate ();
return true;
}
/**
* Returns true if the project is of current version.
* @return true if the project is of current version, otherwise false.
*/
public synchronized boolean isCurrent () {
// if (this.isCurrent == null) {
// if ((this.cfg.getConfigurationFragment("data","http://www.netbeans.org/ns/j2se-project/1",true) != null) ||
// (this.cfg.getConfigurationFragment("data","http://www.netbeans.org/ns/j2se-project/2",true) != null)) {
// this.isCurrent = Boolean.FALSE;
// } else {
// this.isCurrent = Boolean.TRUE;
// }
// }
// return isCurrent.booleanValue();
return true;
}
private boolean canUpdate () {
if (TRANSPARENT_UPDATE) {
return true;
}
//Ask just once under a single write access
if (alreadyAskedInWriteAccess) {
return false;
}
else {
boolean canUpdate = this.notifier.canUpdate();
if (!canUpdate) {
alreadyAskedInWriteAccess = true;
ProjectManager.mutex().postReadRequest(new Runnable() {
public void run() {
alreadyAskedInWriteAccess = false;
}
});
}
return canUpdate;
}
}
private void saveUpdate () throws IOException {
this.helper.putPrimaryConfigurationData(getUpdatedSharedConfigurationData(),true);
// this.cfg.removeConfigurationFragment("data","http://www.netbeans.org/ns/j2se-project/1",true); //NOI18N
// this.cfg.removeConfigurationFragment("data","http://www.netbeans.org/ns/j2se-project/2",true); //NOI18N
ProjectManager.getDefault().saveProject (this.project);
synchronized(this) {
this.isCurrent = Boolean.TRUE;
}
}
private synchronized Element getUpdatedSharedConfigurationData () {
if (cachedElement == null) {
// Element oldRoot = this.cfg.getConfigurationFragment("data","http://www.netbeans.org/ns/j2se-project/1",true); //NOI18N
// if (oldRoot != null) {
// Document doc = oldRoot.getOwnerDocument();
// Element newRoot = doc.createElementNS (RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE,"data"); //NOI18N
// copyDocument (doc, oldRoot, newRoot);
// Element sourceRoots = doc.createElementNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE,"source-roots"); //NOI18N
// Element root = doc.createElementNS (RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE,"root"); //NOI18N
// root.setAttribute ("id","src.dir"); //NOI18N
// sourceRoots.appendChild(root);
// newRoot.appendChild (sourceRoots);
// Element testRoots = doc.createElementNS(RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE,"test-roots"); //NOI18N
// root = doc.createElementNS (RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE,"root"); //NOI18N
// root.setAttribute ("id","test.src.dir"); //NOI18N
// testRoots.appendChild (root);
// newRoot.appendChild (testRoots);
// cachedElement = updateMinAntVersion (newRoot, doc);
// } else {
// oldRoot = this.cfg.getConfigurationFragment("data","http://www.netbeans.org/ns/j2se-project/2",true); //NOI18N
// if (oldRoot != null) {
// Document doc = oldRoot.getOwnerDocument();
// Element newRoot = doc.createElementNS (RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE,"data"); //NOI18N
// copyDocument (doc, oldRoot, newRoot);
// cachedElement = updateMinAntVersion (newRoot, doc);
// }
// }
}
return cachedElement;
}
private synchronized EditableProperties getUpdatedProjectProperties () {
EditableProperties cachedProperties = this.helper.getProperties(RakeProjectHelper.PROJECT_PROPERTIES_PATH);
return cachedProperties;
}
// private static Element updateMinAntVersion (final Element root, final Document doc) {
// NodeList list = root.getElementsByTagNameNS (RubyProjectType.PROJECT_CONFIGURATION_NAMESPACE,MINIMUM_ANT_VERSION_ELEMENT);
// if (list.getLength() == 1) {
// Element me = (Element) list.item(0);
// list = me.getChildNodes();
// if (list.getLength() == 1) {
// me.replaceChild (doc.createTextNode(RubyProjectGenerator.MINIMUM_ANT_VERSION), list.item(0));
// return root;
// }
// }
// assert false : "Invalid project file"; //NOI18N
// return root;
// }
//
/**
* Creates an default Notifier. The default notifier displays a dialog warning user about project update.
* @return notifier
*/
public static Notifier createDefaultNotifier () {
return new Notifier() {
public boolean canUpdate() {
JButton updateOption = new JButton (NbBundle.getMessage(UpdateHelper.class, "CTL_UpdateOption"));
updateOption.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(UpdateHelper.class, "AD_UpdateOption"));
return DialogDisplayer.getDefault().notify(
new NotifyDescriptor (NbBundle.getMessage(UpdateHelper.class,"TXT_ProjectUpdate", BUILD_NUMBER),
NbBundle.getMessage(UpdateHelper.class,"TXT_ProjectUpdateTitle"),
NotifyDescriptor.DEFAULT_OPTION,
NotifyDescriptor.WARNING_MESSAGE,
new Object[] {
updateOption,
NotifyDescriptor.CANCEL_OPTION
},
updateOption)) == updateOption;
}
};
}
/**
* Interface used by the UpdateHelper to ask user about
* the project update.
*/
public static interface Notifier {
/**
* Asks user to update the project
* @return true if the project should be updated
*/
public boolean canUpdate ();
}
}