/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2008 Sun Microsystems, Inc. All rights reserved.
*
* 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. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun 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.scala.project.ui;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.CharConversionException;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.java.platform.JavaPlatform;
import org.netbeans.api.java.platform.JavaPlatformManager;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.modules.java.api.common.SourceRoots;
import org.netbeans.modules.java.api.common.ant.UpdateHelper;
import org.netbeans.modules.java.api.common.project.ProjectProperties;
import org.netbeans.modules.java.api.common.project.ui.LogicalViewProvider2;
import org.netbeans.modules.scala.project.J2SEActionProvider;
import org.netbeans.modules.scala.project.J2SEProjectUtil;
import org.netbeans.modules.scala.project.ui.customizer.J2SEProjectProperties;
import org.netbeans.modules.scala.project.J2SEProject;
import org.netbeans.spi.java.project.support.ui.BrokenReferencesSupport;
import org.netbeans.spi.java.project.support.ui.PackageView;
import org.netbeans.spi.project.ActionProvider;
import org.netbeans.spi.project.SubprojectProvider;
import org.netbeans.spi.project.support.ant.PropertyEvaluator;
import org.netbeans.spi.project.support.ant.ReferenceHelper;
import org.netbeans.spi.project.ui.support.CommonProjectActions;
import org.netbeans.spi.project.ui.support.NodeFactorySupport;
import org.netbeans.spi.project.ui.support.DefaultProjectOperations;
import org.netbeans.spi.project.ui.support.ProjectSensitiveActions;
import org.openide.ErrorManager;
import org.openide.actions.FindAction;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.modules.SpecificationVersion;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Node;
import org.openide.util.ChangeSupport;
import org.openide.util.HelpCtx;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
import org.openide.util.RequestProcessor;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import org.openide.util.actions.SystemAction;
import org.openide.util.lookup.Lookups;
import org.openide.xml.XMLUtil;
/**
* Support for creating logical views.
* @author Petr Hrebejk
*/
public class J2SELogicalViewProvider implements LogicalViewProvider2 {
private static final RequestProcessor RP = new RequestProcessor("J2SEPhysicalViewProvider.RP"); // NOI18N
private final J2SEProject project;
private final UpdateHelper helper;
private final PropertyEvaluator evaluator;
private final SubprojectProvider spp;
private final ReferenceHelper resolver;
private final ChangeSupport changeSupport = new ChangeSupport(this);
public J2SELogicalViewProvider(J2SEProject project, UpdateHelper helper, PropertyEvaluator evaluator, SubprojectProvider spp, ReferenceHelper resolver) {
this.project = project;
assert project != null;
this.helper = helper;
assert helper != null;
this.evaluator = evaluator;
assert evaluator != null;
this.spp = spp;
assert spp != null;
this.resolver = resolver;
assert resolver != null;
}
public Node createLogicalView() {
return new J2SELogicalViewRootNode();
}
public PropertyEvaluator getEvaluator() {
return evaluator;
}
public ReferenceHelper getRefHelper() {
return resolver;
}
public UpdateHelper getUpdateHelper() {
return helper;
}
public Node findPath(Node root, Object target) {
Project prj = root.getLookup().lookup(Project.class);
if (prj == null) {
return null;
}
if (target instanceof FileObject) {
FileObject fo = (FileObject) target;
Project owner = FileOwnerQuery.getOwner(fo);
if (!prj.equals(owner)) {
return null; // Don't waste time if project does not own the fo
}
for (Node n : root.getChildren().getNodes(true)) {
Node result = PackageView.findPath(n, target);
if (result != null) {
return result;
}
}
}
return null;
}
public void addChangeListener(ChangeListener l) {
changeSupport.addChangeListener(l);
}
public void removeChangeListener(ChangeListener l) {
changeSupport.removeChangeListener(l);
}
/**
* Used by J2SEProjectCustomizer to mark the project as broken when it warns user
* about project's broken references and advices him to use BrokenLinksAction to correct it.
*
*/
public void testBroken() {
changeSupport.fireChange();
}
// Private innerclasses ----------------------------------------------------
private static final String[] BREAKABLE_PROPERTIES = {
ProjectProperties.JAVAC_CLASSPATH,
ProjectProperties.RUN_CLASSPATH,
J2SEProjectProperties.DEBUG_CLASSPATH,
ProjectProperties.RUN_TEST_CLASSPATH,
J2SEProjectProperties.DEBUG_TEST_CLASSPATH,
ProjectProperties.JAVAC_TEST_CLASSPATH,
};
public boolean hasBrokenLinks () {
return BrokenReferencesSupport.isBroken(helper.getAntProjectHelper(), resolver, getBreakableProperties(),
new String[] {J2SEProjectProperties.JAVA_PLATFORM});
}
public boolean hasInvalidJdkVersion () {
String javaSource = this.evaluator.getProperty("javac.source"); //NOI18N
String javaTarget = this.evaluator.getProperty("javac.target"); //NOI18N
if (javaSource == null && javaTarget == null) {
//No need to check anything
return false;
}
final String platformId = this.evaluator.getProperty("platform.active"); //NOI18N
final JavaPlatform activePlatform = J2SEProjectUtil.getJavaActivePlatform (platformId);
if (activePlatform == null) {
return true;
}
SpecificationVersion platformVersion = activePlatform.getSpecification().getVersion();
try {
return (javaSource != null && new SpecificationVersion (javaSource).compareTo(platformVersion)>0)
|| (javaTarget != null && new SpecificationVersion (javaTarget).compareTo(platformVersion)>0);
} catch (NumberFormatException nfe) {
ErrorManager.getDefault().log("Invalid javac.source: "+javaSource+" or javac.target: "+javaTarget+" of project:"
+this.project.getProjectDirectory().getPath());
return true;
}
}
private boolean isCompileOnSaveDisabled() {
return !J2SEProjectUtil.isCompileOnSaveEnabled(project) && J2SEProjectUtil.isCompileOnSaveSupported(project);
}
private String[] getBreakableProperties() {
SourceRoots roots = this.project.getSourceRoots();
String[] srcRootProps = roots.getRootProperties();
roots = this.project.getTestSourceRoots();
String[] testRootProps = roots.getRootProperties();
String[] result = new String [BREAKABLE_PROPERTIES.length + srcRootProps.length + testRootProps.length];
System.arraycopy(BREAKABLE_PROPERTIES, 0, result, 0, BREAKABLE_PROPERTIES.length);
System.arraycopy(srcRootProps, 0, result, BREAKABLE_PROPERTIES.length, srcRootProps.length);
System.arraycopy(testRootProps, 0, result, BREAKABLE_PROPERTIES.length + srcRootProps.length, testRootProps.length);
return result;
}
private static Image brokenProjectBadge = ImageUtilities.loadImage("org/netbeans/modules/scala/project/ui/resources/brokenProjectBadge.gif", true);
private static final String COMPILE_ON_SAVE_DISABLED_BADGE_PATH = "org/netbeans/modules/scala/project/ui/resources/compileOnSaveDisabledBadge.gif";
private static final Image compileOnSaveDisabledBadge;
static {
URL errorBadgeIconURL = J2SELogicalViewProvider.class.getClassLoader().getResource(COMPILE_ON_SAVE_DISABLED_BADGE_PATH);
String compileOnSaveDisabledTP = "<img src=\"" + errorBadgeIconURL + "\"> " + NbBundle.getMessage(J2SELogicalViewProvider.class, "TP_CompileOnSaveDisabled");
compileOnSaveDisabledBadge = ImageUtilities.assignToolTipToImage(ImageUtilities.loadImage(COMPILE_ON_SAVE_DISABLED_BADGE_PATH), compileOnSaveDisabledTP); // NOI18N
}
/** Filter node containin additional features for the J2SE physical
*/
private final class J2SELogicalViewRootNode extends AbstractNode {
private Action brokenLinksAction;
private boolean broken; //Represents a state where project has a broken reference repairable by broken reference support
private boolean illegalState; //Represents a state where project is not in legal state, eg invalid source/target level
private boolean compileOnSaveDisabled; //true iff Compile-on-Save is disabled
public J2SELogicalViewRootNode() {
super(NodeFactorySupport.createCompositeChildren(project, "Projects/org-netbeans-modules-scala-project/Nodes"),
Lookups.singleton(project));
setIconBaseWithExtension("org/netbeans/modules/scala/project/ui/resources/scalaProject.png");
super.setName( ProjectUtils.getInformation( project ).getDisplayName() );
if (hasBrokenLinks()) {
broken = true;
}
else if (hasInvalidJdkVersion ()) {
illegalState = true;
}
compileOnSaveDisabled = isCompileOnSaveDisabled();
brokenLinksAction = new BrokenLinksAction();
}
@Override
public String getShortDescription() {
String prjDirDispName = FileUtil.getFileDisplayName(project.getProjectDirectory());
return NbBundle.getMessage(J2SELogicalViewProvider.class, "HINT_project_root_node", prjDirDispName);
}
@Override
public String getHtmlDisplayName() {
String dispName = super.getDisplayName();
try {
dispName = XMLUtil.toElementContent(dispName);
} catch (CharConversionException ex) {
return dispName;
}
// XXX text colors should be taken from UIManager, not hard-coded!
return broken || illegalState ? "<font color=\"#A40000\">" + dispName + "</font>" : null; //NOI18N
}
@Override
public Image getIcon(int type) {
Image original = super.getIcon(type);
if (broken || illegalState) {
return ImageUtilities.mergeImages(original, brokenProjectBadge, 8, 0);
} else {
return compileOnSaveDisabled ? ImageUtilities.mergeImages(original, compileOnSaveDisabledBadge, 8, 0) : original;
}
}
@Override
public Image getOpenedIcon(int type) {
Image original = super.getOpenedIcon(type);
if (broken || illegalState) {
return ImageUtilities.mergeImages(original, brokenProjectBadge, 8, 0);
} else {
return compileOnSaveDisabled ? ImageUtilities.mergeImages(original, compileOnSaveDisabledBadge, 8, 0) : original;
}
}
@Override
public Action[] getActions( boolean context ) {
return getAdditionalActions();
}
@Override
public boolean canRename() {
return true;
}
@Override
public void setName(String s) {
DefaultProjectOperations.performDefaultRenameOperation(project, s);
}
@Override
public HelpCtx getHelpCtx() {
return new HelpCtx(J2SELogicalViewRootNode.class);
}
// Private methods -------------------------------------------------
private Action[] getAdditionalActions() {
ResourceBundle bundle = NbBundle.getBundle(J2SELogicalViewProvider.class);
List<Action> actions = new ArrayList<Action>();
actions.add(CommonProjectActions.newFileAction());
actions.add(null);
actions.add(ProjectSensitiveActions.projectCommandAction(J2SEActionProvider.COMMAND_SCALA_CONSOLE, bundle.getString("LBL_ScalaConsole_Name"), null)); // NOI18N
actions.add(ProjectSensitiveActions.projectCommandAction(ActionProvider.COMMAND_BUILD, bundle.getString("LBL_BuildAction_Name"), null)); // NOI18N
actions.add(ProjectSensitiveActions.projectCommandAction(ActionProvider.COMMAND_REBUILD, bundle.getString("LBL_RebuildAction_Name"), null)); // NOI18N
actions.add(ProjectSensitiveActions.projectCommandAction(ActionProvider.COMMAND_CLEAN, bundle.getString("LBL_CleanAction_Name"), null)); // NOI18N
actions.add(ProjectSensitiveActions.projectCommandAction(JavaProjectConstants.COMMAND_JAVADOC, bundle.getString("LBL_JavadocAction_Name"), null)); // NOI18N
actions.add(null);
actions.add(ProjectSensitiveActions.projectCommandAction(ActionProvider.COMMAND_RUN, bundle.getString("LBL_RunAction_Name"), null)); // NOI18N
actions.addAll(Utilities.actionsForPath("Projects/Debugger_Actions_temporary")); //NOI18N
actions.addAll(Utilities.actionsForPath("Projects/Profiler_Actions_temporary")); //NOI18N
actions.add(ProjectSensitiveActions.projectCommandAction(ActionProvider.COMMAND_TEST, bundle.getString("LBL_TestAction_Name"), null)); // NOI18N
actions.add(CommonProjectActions.setProjectConfigurationAction());
actions.add(null);
actions.add(CommonProjectActions.setAsMainProjectAction());
actions.add(CommonProjectActions.openSubprojectsAction());
actions.add(CommonProjectActions.closeProjectAction());
actions.add(null);
actions.add(CommonProjectActions.renameProjectAction());
actions.add(CommonProjectActions.moveProjectAction());
actions.add(CommonProjectActions.copyProjectAction());
actions.add(CommonProjectActions.deleteProjectAction());
actions.add(null);
actions.add(SystemAction.get(FindAction.class));
// honor 57874 contact
actions.addAll(Utilities.actionsForPath("Projects/Actions")); //NOI18N
actions.add(null);
if (broken) {
actions.add(brokenLinksAction);
}
actions.add(CommonProjectActions.customizeProjectAction());
return actions.toArray(new Action[actions.size()]);
}
private void setBroken(boolean broken) {
this.broken = broken;
brokenLinksAction.setEnabled(broken);
fireIconChange();
fireOpenedIconChange();
fireDisplayNameChange(null, null);
}
private void setIllegalState (boolean illegalState) {
this.illegalState = illegalState;
fireIconChange();
fireOpenedIconChange();
fireDisplayNameChange(null, null);
}
private void setCompileOnSaveDisabled (boolean value) {
this.compileOnSaveDisabled = value;
fireIconChange();
fireOpenedIconChange();
fireDisplayNameChange(null, null);
}
/** This action is created only when project has broken references.
* Once these are resolved the action is disabled.
*/
private class BrokenLinksAction extends AbstractAction implements PropertyChangeListener, ChangeListener, Runnable {
private RequestProcessor.Task task = null;
private PropertyChangeListener weakPCL;
public BrokenLinksAction() {
putValue(Action.NAME, NbBundle.getMessage(J2SELogicalViewProvider.class, "LBL_Fix_Broken_Links_Action"));
setEnabled(broken);
evaluator.addPropertyChangeListener(this);
// When evaluator fires changes that platform properties were
// removed the platform still exists in JavaPlatformManager.
// That's why I have to listen here also on JPM:
weakPCL = WeakListeners.propertyChange(this, JavaPlatformManager.getDefault());
JavaPlatformManager.getDefault().addPropertyChangeListener(weakPCL);
J2SELogicalViewProvider.this.addChangeListener(WeakListeners.change(this, J2SELogicalViewProvider.this));
}
public void actionPerformed(ActionEvent e) {
try {
helper.requestUpdate();
BrokenReferencesSupport.showCustomizer(helper.getAntProjectHelper(), resolver, getBreakableProperties(), new String[] {J2SEProjectProperties.JAVA_PLATFORM});
run();
} catch (IOException ioe) {
ErrorManager.getDefault().notify(ioe);
}
}
public void propertyChange(PropertyChangeEvent evt) {
refsMayChanged();
}
public void stateChanged(ChangeEvent evt) {
refsMayChanged();
}
public synchronized void run() {
boolean old = J2SELogicalViewRootNode.this.broken;
boolean broken = hasBrokenLinks();
if (old != broken) {
setBroken(broken);
}
old = J2SELogicalViewRootNode.this.illegalState;
broken = hasInvalidJdkVersion ();
if (old != broken) {
setIllegalState(broken);
}
old = J2SELogicalViewRootNode.this.compileOnSaveDisabled;
boolean cosDisabled = isCompileOnSaveDisabled();
if (old != cosDisabled) {
setCompileOnSaveDisabled(cosDisabled);
}
}
private void refsMayChanged() {
// check project state whenever there was a property change
// or change in list of platforms.
// Coalesce changes since they can come quickly:
if (task == null) {
task = RP.create(this);
}
task.schedule(100);
}
}
}
}