/* * 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); } } } }