--- a/java.j2seproject/nbproject/project.xml +++ a/java.j2seproject/nbproject/project.xml @@ -159,7 +159,7 @@ 1 - 1.46 + 1.48 @@ -212,7 +212,7 @@ 1 - 1.59 + 1.60 --- a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java +++ a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/J2SEProject.java @@ -58,14 +58,10 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.WeakHashMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -79,6 +75,8 @@ import org.netbeans.api.annotations.common.NonNull; import org.netbeans.api.java.classpath.ClassPath; import org.netbeans.api.java.classpath.GlobalPathRegistry; +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.java.project.classpath.ProjectClassPathModifier; import org.netbeans.api.project.Project; @@ -88,7 +86,6 @@ import org.netbeans.api.project.ant.AntBuildExtender; import org.netbeans.api.project.libraries.Library; import org.netbeans.api.project.libraries.LibraryManager; -import org.netbeans.api.project.ui.OpenProjects; import org.netbeans.api.queries.FileBuiltQuery.Status; import org.netbeans.modules.java.api.common.Roots; import org.netbeans.modules.java.api.common.SourceRoots; @@ -139,7 +136,6 @@ import org.openide.util.Mutex; import org.openide.util.MutexException; import org.openide.util.NbBundle; -import org.openide.util.RequestProcessor; import org.openide.util.lookup.Lookups; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -151,9 +147,9 @@ import static org.netbeans.spi.project.support.ant.GeneratedFilesHelper.FLAG_OLD_STYLESHEET; import static org.netbeans.spi.project.support.ant.GeneratedFilesHelper.FLAG_UNKNOWN; import org.netbeans.spi.whitelist.support.WhiteListQueryMergerSupport; -import org.openide.filesystems.FileLock; import org.openide.filesystems.URLMapper; import org.openide.modules.SpecificationVersion; +import org.openide.xml.XMLUtil; /** * Represents one plain J2SE project. * @author Jesse Glick, et al. @@ -189,7 +185,6 @@ }; private static final Icon J2SE_PROJECT_ICON = ImageUtilities.loadImageIcon("org/netbeans/modules/java/j2seproject/ui/resources/j2seProject.png", false); // NOI18N private static final Logger LOG = Logger.getLogger(J2SEProject.class.getName()); - private static final RequestProcessor RP = new RequestProcessor(J2SEProject.class.getName(), 1); private final AuxiliaryConfiguration aux; private final AntProjectHelper helper; @@ -362,7 +357,9 @@ } private Lookup createLookup(final AuxiliaryConfiguration aux) { - FileEncodingQueryImplementation encodingQuery = QuerySupport.createFileEncodingQuery(evaluator(), J2SEProjectProperties.SOURCE_ENCODING); + final FileEncodingQueryImplementation encodingQuery = QuerySupport.createFileEncodingQuery(evaluator(), J2SEProjectProperties.SOURCE_ENCODING); + final J2SELogicalViewProvider lvp = new J2SELogicalViewProvider(this, this.updateHelper, evaluator(), refHelper); + final Runnable hook = new PlatformChangedHook(); final Lookup base = Lookups.fixed( J2SEProject.this, QuerySupport.createProjectInformation(updateHelper, this, J2SE_PROJECT_ICON), @@ -370,7 +367,7 @@ helper.createCacheDirectoryProvider(), helper.createAuxiliaryProperties(), refHelper.createSubprojectProvider(), - new J2SELogicalViewProvider(this, this.updateHelper, evaluator(), refHelper), + lvp, // new J2SECustomizerProvider(this, this.updateHelper, evaluator(), refHelper), new CustomizerProviderImpl(this, this.updateHelper, evaluator(), refHelper, this.genFilesHelper), LookupMergerSupport.createClassPathProviderMerger(cpProvider), @@ -404,7 +401,10 @@ QuerySupport.createBinaryForSourceQueryImplementation(this.sourceRoots, this.testRoots, this.helper, this.evaluator()), //Does not use APH to get/put properties/cfgdata QuerySupport.createAnnotationProcessingQuery(this.helper, this.evaluator(), ProjectProperties.ANNOTATION_PROCESSING_ENABLED, ProjectProperties.ANNOTATION_PROCESSING_ENABLED_IN_EDITOR, ProjectProperties.ANNOTATION_PROCESSING_RUN_ALL_PROCESSORS, ProjectProperties.ANNOTATION_PROCESSING_PROCESSORS_LIST, ProjectProperties.ANNOTATION_PROCESSING_SOURCE_OUTPUT, ProjectProperties.ANNOTATION_PROCESSING_PROCESSOR_OPTIONS), LookupProviderSupport.createActionProviderMerger(), - WhiteListQueryMergerSupport.createWhiteListQueryMerger() + WhiteListQueryMergerSupport.createWhiteListQueryMerger(), + BrokenReferencesSupport.createReferenceProblemsProvider(helper, refHelper, eval, lvp.getBreakableProperties(), lvp.getPlatformProperties()), + BrokenReferencesSupport.createPlatformVersionProblemProvider(helper, eval, hook, JavaPlatform.getDefault().getSpecification().getName(), J2SEProjectProperties.JAVA_PLATFORM, J2SEProjectProperties.JAVAC_SOURCE, J2SEProjectProperties.JAVAC_TARGET), + UILookupMergerSupport.createProjectProblemsProviderMerger() ); lookup = base; // in case LookupProvider's call Project.getLookup return LookupProviderSupport.createCompositeLookup(base, "Projects/org-netbeans-modules-java-j2seproject/Lookup"); //NOI18N @@ -676,29 +676,6 @@ LOG.log(Level.WARNING, "Unsupported charset: {0} in project: {1}", new Object[]{prop, FileUtil.getFileDisplayName(getProjectDirectory())}); //NOI18N } } - RP.post(new Runnable() { - @Override - public void run() { - final Future projects = OpenProjects.getDefault().openProjects(); - try { - projects.get(); - } catch (ExecutionException ex) { - Exceptions.printStackTrace(ex); - } catch (InterruptedException ie) { - Exceptions.printStackTrace(ie); - } - J2SELogicalViewProvider physicalViewProvider = getLookup().lookup(J2SELogicalViewProvider.class); - if (physicalViewProvider != null && physicalViewProvider.hasBrokenLinks()) { - BrokenReferencesSupport.showAlert( - helper, - refHelper, - eval, - physicalViewProvider.getBreakableProperties(), - physicalViewProvider.getPlatformProperties()); - } - } - }); - //Update per project CopyLibs if needed new UpdateCopyLibs(J2SEProject.this).run(); @@ -1059,4 +1036,61 @@ } + private final class PlatformChangedHook implements Runnable { + @Override + public void run() { + final String platformId = evaluator().getProperty(J2SEProjectProperties.JAVA_PLATFORM); + if (platformId == null) { + return; + } + JavaPlatform platform = null; + for (JavaPlatform jp : JavaPlatformManager.getDefault().getPlatforms(null, null)) { + if (platformId.equals(jp.getProperties().get("platform.ant.name"))) { //NOI18N + platform = jp; + break; + } + } + if (platform == null) { + return; + } + final boolean remove = platform.equals(JavaPlatformManager.getDefault().getDefaultPlatform()); + final Element root = helper.getPrimaryConfigurationData(true); + boolean changed = false; + if (remove) { + final Element platformElement = XMLUtil.findElement( + root, + "explicit-platform", //NOI18N + PROJECT_CONFIGURATION_NAMESPACE); + if (platformElement != null) { + root.removeChild(platformElement); + changed = true; + } + } else { + Element insertBefore = null; + for (Element e : XMLUtil.findSubElements(root)) { + final String name = e.getNodeName(); + if (! "name".equals(name) && //NOI18N + ! "minimum-ant-version".equals(name)) { //NOI18N + insertBefore = e; + break; + } + } + final Element platformNode = insertBefore.getOwnerDocument().createElementNS( + PROJECT_CONFIGURATION_NAMESPACE, + "explicit-platform"); //NOI18N + platformNode.setAttribute( + "explicit-source-supported", //NOI18N + platform.getSpecification().getVersion().compareTo(new SpecificationVersion("1.3"))>0? //NOI18N + "true": //NOI18N + "false"); //NOI18N + root.insertBefore(platformNode, insertBefore); + changed = true; + } + if (changed) { + helper.putPrimaryConfigurationData(root, true); + } + } + + } + } --- a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/ui/Bundle.properties +++ a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/ui/Bundle.properties @@ -54,8 +54,6 @@ ACSD_Customizer_Ok_Option=N/A ACSD_Customizer_Cancel_Option=N/A -LBL_Fix_Broken_Links_Action=Resolve Reference Problems... - AD_J2SECustomizerProviderOk=N/A AD_J2SECustomizerProviderCancel=N/A --- a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/ui/J2SELogicalViewProvider.java +++ a/java.j2seproject/src/org/netbeans/modules/java/j2seproject/ui/J2SELogicalViewProvider.java @@ -46,24 +46,15 @@ import java.awt.Color; 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.lang.ref.WeakReference; import java.net.URL; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.netbeans.api.annotations.common.NonNull; -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; @@ -71,40 +62,33 @@ import org.netbeans.api.project.ProjectManager; import org.netbeans.api.project.ProjectUtils; import org.netbeans.api.project.SourceGroup; -import org.netbeans.api.project.libraries.LibraryManager; +import org.netbeans.api.project.ui.ProjectProblems; 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.java.api.common.util.CommonProjectUtils; import org.netbeans.modules.java.j2seproject.J2SEProjectUtil; import org.netbeans.modules.java.j2seproject.ui.customizer.J2SEProjectProperties; import org.netbeans.modules.java.j2seproject.J2SEProject; -import org.netbeans.spi.java.project.support.ui.BrokenReferencesSupport; import org.netbeans.spi.java.project.support.ui.PackageView; import org.netbeans.spi.project.support.ant.PropertyEvaluator; import org.netbeans.spi.project.support.ant.ReferenceHelper; +import org.netbeans.spi.project.ui.ProjectProblemsProvider; 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.openide.ErrorManager; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; -import org.openide.awt.ActionRegistration; -import org.openide.awt.DynamicMenuContent; +import org.openide.awt.ActionReferences; 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.ContextAwareAction; import org.openide.util.HelpCtx; import org.openide.util.ImageUtilities; -import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; -import org.openide.util.Utilities; import org.openide.util.WeakListeners; import org.openide.util.lookup.Lookups; import org.openide.xml.XMLUtil; @@ -113,9 +97,32 @@ * Support for creating logical views. * @author Petr Hrebejk */ +@ActionReferences({ + @ActionReference( + id=@ActionID(id="org.netbeans.modules.project.ui.problems.BrokenProjectActionFactory",category="Project"), + position = 2600, + path = "Projects/org-netbeans-modules-java-j2seproject/Actions") +}) public class J2SELogicalViewProvider implements LogicalViewProvider2 { private static final RequestProcessor RP = new RequestProcessor(J2SELogicalViewProvider.class); + private static final String[] BREAKABLE_PROPERTIES = { + ProjectProperties.JAVAC_CLASSPATH, + ProjectProperties.RUN_CLASSPATH, + J2SEProjectProperties.DEBUG_CLASSPATH, + ProjectProperties.RUN_TEST_CLASSPATH, + J2SEProjectProperties.DEBUG_TEST_CLASSPATH, + ProjectProperties.ENDORSED_CLASSPATH, + ProjectProperties.JAVAC_TEST_CLASSPATH, + }; + private static final String COMPILE_ON_SAVE_DISABLED_BADGE_PATH = "org/netbeans/modules/java/j2seproject/ui/resources/compileOnSaveDisabledBadge.gif"; + private static final Image compileOnSaveDisabledBadge; + + static { + URL errorBadgeIconURL = J2SELogicalViewProvider.class.getClassLoader().getResource(COMPILE_ON_SAVE_DISABLED_BADGE_PATH); + String compileOnSaveDisabledTP = " " + NbBundle.getMessage(J2SELogicalViewProvider.class, "TP_CompileOnSaveDisabled"); + compileOnSaveDisabledBadge = ImageUtilities.assignToolTipToImage(ImageUtilities.loadImage(COMPILE_ON_SAVE_DISABLED_BADGE_PATH), compileOnSaveDisabledTP); // NOI18N + } private final J2SEProject project; private final UpdateHelper helper; @@ -123,8 +130,16 @@ private final ReferenceHelper resolver; private final ChangeSupport changeSupport = new ChangeSupport(this); private final PropertyChangeListener pcl; - private Map activeLibManLocs; + private final RequestProcessor.Task task = RP.create(new Runnable() { + public @Override void run() { + setBroken(ProjectProblems.isBroken(project)); + setCompileOnSaveDisabled(isCompileOnSaveDisabled()); + } + }); + private volatile boolean listenersInited; + private volatile boolean broken; //Represents a state where project has a broken reference repairable by broken reference support + private volatile boolean compileOnSaveDisabled; //true iff Compile-on-Save is disabled public J2SELogicalViewProvider(J2SEProject project, UpdateHelper helper, PropertyEvaluator evaluator, ReferenceHelper resolver) { this.project = project; @@ -137,10 +152,13 @@ assert resolver != null; pcl = new PropertyChangeListener() { public @Override void propertyChange(PropertyChangeEvent evt) { - if (LibraryManager.PROP_OPEN_LIBRARY_MANAGERS.equals(evt.getPropertyName())) { - addLibraryManagerListener(); + final String propName = evt.getPropertyName(); + if (propName == null || + ProjectProblemsProvider.PROP_PROBLEMS.equals(evt.getPropertyName()) || + ProjectProperties.COMPILE_ON_SAVE.equals(evt.getPropertyName()) || + propName.startsWith(ProjectProperties.COMPILE_ON_SAVE_UNSUPPORTED_PREFIX)) { + testBroken(); } - testBroken(); } }; } @@ -155,58 +173,19 @@ synchronized (J2SELogicalViewProvider.class) { if (!listenersInited) { evaluator.addPropertyChangeListener(pcl); - JavaPlatformManager.getDefault().addPropertyChangeListener(WeakListeners.propertyChange(pcl, JavaPlatformManager.getDefault())); - LibraryManager.addOpenManagersPropertyChangeListener(new OpenManagersWeakListener(pcl)); - addLibraryManagerListener(); + final ProjectProblemsProvider ppp = project.getLookup().lookup(ProjectProblemsProvider.class); + if (ppp != null) { + ppp.addPropertyChangeListener(pcl); + } listenersInited = true; } } } }); } - - private void addLibraryManagerListener() { - final Map oldLMs; - final boolean attachToDefault; - synchronized (this) { - attachToDefault = activeLibManLocs == null; - if (attachToDefault) { - activeLibManLocs = new HashMap(); - } - oldLMs = new HashMap(activeLibManLocs); - } - if (attachToDefault) { - final LibraryManager manager = LibraryManager.getDefault(); - manager.addPropertyChangeListener(WeakListeners.propertyChange(pcl, manager)); - } - final Collection managers = LibraryManager.getOpenManagers(); - final Map managerByLocation = new HashMap(); - for (LibraryManager manager : managers) { - final URL url = manager.getLocation(); - if (url != null) { - managerByLocation.put(url, manager); - } - } - final HashMap toRemove = new HashMap(oldLMs); - toRemove.keySet().removeAll(managerByLocation.keySet()); - for (Object[] pair : toRemove.values()) { - ((LibraryManager)pair[0]).removePropertyChangeListener((PropertyChangeListener)pair[1]); - } - managerByLocation.keySet().removeAll(oldLMs.keySet()); - final HashMap toAdd = new HashMap(); - for (Map.Entry e : managerByLocation.entrySet()) { - final LibraryManager manager = e.getValue(); - final PropertyChangeListener listener = WeakListeners.propertyChange(pcl, manager); - manager.addPropertyChangeListener(listener); - toAdd.put(e.getKey(), new Object[] {manager, listener}); - } - synchronized (this) { - activeLibManLocs.keySet().removeAll(toRemove.keySet()); - activeLibManLocs.putAll(toAdd); - } - } - @Override public Node createLogicalView() { + @Override + public Node createLogicalView() { initListeners(); return new J2SELogicalViewRootNode(); } @@ -264,9 +243,7 @@ } return false; } - - - + public void addChangeListener(ChangeListener l) { changeSupport.addChangeListener(l); } @@ -274,80 +251,12 @@ public void removeChangeListener(ChangeListener l) { changeSupport.removeChangeListener(l); } - - private final RequestProcessor.Task task = RP.create(new Runnable() { - public @Override void run() { - boolean old = broken; - boolean _broken = hasBrokenLinks(); - if (old != _broken) { - setBroken(_broken); - } - old = illegalState; - boolean _illegalState = hasInvalidJdkVersion(); - if (old != _illegalState) { - setIllegalState(_illegalState); - } - old = compileOnSaveDisabled; - boolean _compileOnSaveDisabled = isCompileOnSaveDisabled(); - if (old != _compileOnSaveDisabled) { - setCompileOnSaveDisabled(_compileOnSaveDisabled); - } - } - }); - /** - * Used by J2SEProjectCustomizer to mark the project as broken when it warns user - * about project's broken references and advises him to use BrokenLinksAction to correct it. - */ - public @Override void testBroken() { + @Override + public void testBroken() { task.schedule(500); } - - // 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.ENDORSED_CLASSPATH, - ProjectProperties.JAVAC_TEST_CLASSPATH, - }; - - public boolean hasBrokenLinks () { - return BrokenReferencesSupport.isBroken(helper.getAntProjectHelper(), resolver, getBreakableProperties(), - new String[] {J2SEProjectProperties.JAVA_PLATFORM}); - } - - private 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 = CommonProjectUtils.getActivePlatform (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); - } - public String[] getBreakableProperties() { SourceRoots roots = this.project.getSourceRoots(); String[] srcRootProps = roots.getRootProperties(); @@ -364,31 +273,20 @@ return new String[] {J2SEProjectProperties.JAVA_PLATFORM}; } - private static Image brokenProjectBadge = ImageUtilities.loadImage("org/netbeans/modules/java/j2seproject/ui/resources/brokenProjectBadge.gif", true); - private static final String COMPILE_ON_SAVE_DISABLED_BADGE_PATH = "org/netbeans/modules/java/j2seproject/ui/resources/compileOnSaveDisabledBadge.gif"; - private static final Image compileOnSaveDisabledBadge; - - static { - URL errorBadgeIconURL = J2SELogicalViewProvider.class.getClassLoader().getResource(COMPILE_ON_SAVE_DISABLED_BADGE_PATH); - String compileOnSaveDisabledTP = " " + NbBundle.getMessage(J2SELogicalViewProvider.class, "TP_CompileOnSaveDisabled"); - compileOnSaveDisabledBadge = ImageUtilities.assignToolTipToImage(ImageUtilities.loadImage(COMPILE_ON_SAVE_DISABLED_BADGE_PATH), compileOnSaveDisabledTP); // NOI18N - } + private boolean isCompileOnSaveDisabled() { + return !J2SEProjectUtil.isCompileOnSaveEnabled(project) && J2SEProjectUtil.isCompileOnSaveSupported(project); + } private final class J2SELogicalViewRootNode extends AbstractNode implements ChangeListener, PropertyChangeListener { - private final ProjectInformation info = ProjectUtils.getInformation(project); + private final ProjectInformation info = ProjectUtils.getInformation(project); @SuppressWarnings("LeakingThisInConstructor") public J2SELogicalViewRootNode() { super(NodeFactorySupport.createCompositeChildren(project, "Projects/org-netbeans-modules-java-j2seproject/Nodes"), Lookups.singleton(project)); setIconBaseWithExtension("org/netbeans/modules/java/j2seproject/ui/resources/j2seProject.png"); - if (hasBrokenLinks()) { - broken = true; - } - else if (hasInvalidJdkVersion ()) { - illegalState = true; - } + broken = ProjectProblems.isBroken(project); compileOnSaveDisabled = isCompileOnSaveDisabled(); addChangeListener(WeakListeners.change(this, J2SELogicalViewProvider.this)); info.addPropertyChangeListener(WeakListeners.propertyChange(this, info)); @@ -408,29 +306,19 @@ } catch (CharConversionException ex) { return dispName; } - return broken || illegalState ? "" + dispName + "" : null; //NOI18N + return broken ? "" + dispName + "" : 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; - } + final Image original = super.getIcon(type); + return !broken && 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; - } + final Image original = super.getOpenedIcon(type); + return !broken && compileOnSaveDisabled ? ImageUtilities.mergeImages(original, compileOnSaveDisabledBadge, 8, 0) : original; } public @Override void stateChanged(ChangeEvent e) { @@ -472,27 +360,24 @@ return new HelpCtx(J2SELogicalViewRootNode.class); } - } - - 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 + } // Private methods ------------------------------------------------- private void setBroken(boolean broken) { - this.broken = broken; - changeSupport.fireChange(); - } - - private void setIllegalState (boolean illegalState) { - this.illegalState = illegalState; - changeSupport.fireChange(); - } + //Weak consistent, only visibility required + if (this.broken != broken) { + this.broken = broken; + changeSupport.fireChange(); + } + } private void setCompileOnSaveDisabled (boolean value) { - this.compileOnSaveDisabled = value; - changeSupport.fireChange(); + //Weak consistent, only visibility required + if (this.compileOnSaveDisabled != value) { + this.compileOnSaveDisabled = value; + changeSupport.fireChange(); + } } private static @NonNull Color getErrorForeground() { @@ -501,80 +386,5 @@ result = Color.RED; } return result; - } - - @ActionID(id = "org.netbeans.modules.java.j2seproject.ui.J2SELogicalViewProvider$BrokenLinksActionFactory", category = "Project") - @ActionRegistration(displayName = "#LBL_Fix_Broken_Links_Action", lazy=false) - @ActionReference(position = 2600, path = "Projects/org-netbeans-modules-java-j2seproject/Actions") - public static final class BrokenLinksActionFactory extends AbstractAction implements ContextAwareAction { - - /** for layer registration */ - public BrokenLinksActionFactory() { - putValue(Action.NAME, NbBundle.getMessage(J2SELogicalViewProvider.class, "LBL_Fix_Broken_Links_Action")); - setEnabled(false); - putValue(DynamicMenuContent.HIDE_WHEN_DISABLED, true); - } - - public @Override void actionPerformed(ActionEvent e) { - assert false; - } - - public @Override Action createContextAwareInstance(Lookup actionContext) { - Collection p = actionContext.lookupAll(Project.class); - if (p.size() != 1) { - return this; - } - J2SELogicalViewProvider lvp = p.iterator().next().getLookup().lookup(J2SELogicalViewProvider.class); - if (lvp == null) { - return this; - } - return lvp.new BrokenLinksAction(); - } - - } - - /** This action is created only when project has broken references. - * Once these are resolved the action is disabled. - */ - private class BrokenLinksAction extends AbstractAction { - - public BrokenLinksAction() { - putValue(Action.NAME, NbBundle.getMessage(J2SELogicalViewProvider.class, "LBL_Fix_Broken_Links_Action")); - setEnabled(broken); - putValue(DynamicMenuContent.HIDE_WHEN_DISABLED, true); - } - - public void actionPerformed(ActionEvent e) { - try { - helper.requestUpdate(); - BrokenReferencesSupport.showCustomizer(helper.getAntProjectHelper(), resolver, getBreakableProperties(), getPlatformProperties()); - testBroken(); - } catch (IOException ioe) { - ErrorManager.getDefault().notify(ioe); - } - } - - } - - private static class OpenManagersWeakListener extends WeakReference implements Runnable, PropertyChangeListener { - - public OpenManagersWeakListener(final PropertyChangeListener listener) { - super(listener, Utilities.activeReferenceQueue()); - } - - @Override - public void run() { - LibraryManager.removeOpenManagersPropertyChangeListener(this); - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - final PropertyChangeListener listener = get(); - if (listener != null) { - listener.propertyChange(evt); - } - } - - } - + } } --- a/java.project/apichanges.xml +++ a/java.project/apichanges.xml @@ -109,6 +109,22 @@ + + + Changed BrokenReferencesSupport to delegate to ProjectProblems + + + + + +

+ Added PreferredProjectPlatform to provide a + JavaPlatform which should be used for a new project. +

+
+ + +
Added PreferredProjectPlatform --- a/java.project/manifest.mf +++ a/java.project/manifest.mf @@ -3,7 +3,7 @@ OpenIDE-Module-Layer: org/netbeans/modules/java/project/layer.xml OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/java/project/Bundle.properties OpenIDE-Module-Needs: javax.script.ScriptEngine.freemarker -OpenIDE-Module-Specification-Version: 1.47 +OpenIDE-Module-Specification-Version: 1.48 OpenIDE-Module-Recommends: org.netbeans.spi.java.project.runner.JavaRunnerImplementation AutoUpdate-Show-In-Client: false --- a/java.project/nbproject/project.xml +++ a/java.project/nbproject/project.xml @@ -59,6 +59,15 @@
+ org.jdesktop.layout + + + + 1 + 1.22 + + + org.netbeans.api.annotations.common @@ -151,7 +160,7 @@ 1 - 1.59 + 1.60 --- a/java.project/src/org/netbeans/modules/java/project/Bundle.properties +++ a/java.project/src/org/netbeans/modules/java/project/Bundle.properties @@ -50,10 +50,6 @@ Templates/Classes=Java -LBL_BrokenLinksCustomizer_Fix=&Resolve... -LBL_BrokenLinksCustomizer_Description=&Description\: -LBL_BrokenLinksCustomizer_List=Reference &Problems: - # JavaTargetChooserPanelGUI LBL_JavaTargetChooserPanelGUI_Name=Name and Location LBL_JavaTargetChooserPanelGUI_jLabel1=&Location\: @@ -82,10 +78,6 @@ # {0} - name of the existing file MSG_file_already_exist=The file {0} already exists. -#BrokenReferencesAlertPanel -MSG_Broken_References_Label=Reference Problems -MSG_Broken_References=One or more project resources could not be found.
Right-click the project in the Projects window and choose
Resolve Reference Problems to find the missing resources. -MSG_Broken_References_Again=&Do not show this message again AD_JavaTargetChooserPanelGUI=N/A @@ -99,26 +91,6 @@ AD_fileTextField=N/A -MSG_BrokenReferencesAlertPanel_notAgain=&Do not show this message again - -ACSN_BrokenReferencesAlertPanel_notAgain=Do not show this message again - -ACSD_BrokenReferencesAlertPanel_notAgain=N/A - -ACSN_BrokenReferencesAlertPanel=Broken References Panel - -ACSD_BrokenReferencesAlertPanel=N/A - -ACSD_BrokenLinksCustomizer_Description=N/A - -ACSD_BrokenLinksCustomizer_List=N/A - -ACSD_BrokenLinksCustomizer_Fix=N/A - -ACSN_BrokenReferencesCustomizer=Broken References Customizer - -ACSD_BrokenReferencesCustomizer=N/A - # PackageDisplayUtils LBL_DefaultPackage= # {0} - full package name @@ -130,3 +102,8 @@ ERR_JavaTargetChooser_CantUseDefaultPackage=Provide a package name. +FixProjectSourceLevel.downgradeLevel.text=&Downgrade Project Source/Binary Format to {0} +FixProjectSourceLevel.jLabel1.text=&Java Platform: +FixProjectSourceLevel.useOtherPlatform.text=Use Other Java &Platform +FixProjectSourceLevel.managePlatforms.text=&Manage Platforms... +FixProjectSourceLevel.jLabel2.text=The project source/binary format requires a newer java platform ({0}). --- a/java.project/src/org/netbeans/modules/java/project/FixProjectSourceLevel.form +++ a/java.project/src/org/netbeans/modules/java/project/FixProjectSourceLevel.form @@ -0,0 +1,128 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
--- a/java.project/src/org/netbeans/modules/java/project/FixProjectSourceLevel.java +++ a/java.project/src/org/netbeans/modules/java/project/FixProjectSourceLevel.java @@ -0,0 +1,235 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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]" + * + * 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. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.java.project; + +import java.awt.Component; +import javax.swing.DefaultComboBoxModel; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JList; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.java.platform.JavaPlatform; +import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.api.java.platform.PlatformsCustomizer; +import org.netbeans.api.java.platform.Specification; +import org.openide.modules.SpecificationVersion; +import org.openide.util.Parameters; + +/** + * + * @author Tomas Zezula + */ +public final class FixProjectSourceLevel extends javax.swing.JPanel { + + private final SpecificationVersion platformVersion; + private final SpecificationVersion minVersion; + + public FixProjectSourceLevel( + @NonNull final String type, + @NonNull final SpecificationVersion minVersion, + @NonNull final SpecificationVersion platformVersion) { + Parameters.notNull("type", type); + Parameters.notNull("minVersion", minVersion); + this.platformVersion = platformVersion; + this.minVersion = minVersion; + initComponents(); + postInit(type, minVersion); + } + + + boolean isDowngradeLevel() { + return this.downgradeLevel.isSelected(); + } + + final JavaPlatform getSelectedPlatform() { + if (!this.useOtherPlatform.isSelected()) { + throw new IllegalStateException(); + } + return (JavaPlatform) platforms.getSelectedItem(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + buttonGroup1 = new javax.swing.ButtonGroup(); + downgradeLevel = new javax.swing.JRadioButton(); + useOtherPlatform = new javax.swing.JRadioButton(); + jLabel1 = new javax.swing.JLabel(); + platforms = new javax.swing.JComboBox(); + managePlatforms = new javax.swing.JButton(); + jLabel2 = new javax.swing.JLabel(); + + buttonGroup1.add(downgradeLevel); + org.openide.awt.Mnemonics.setLocalizedText(downgradeLevel, org.openide.util.NbBundle.getMessage(FixProjectSourceLevel.class, "FixProjectSourceLevel.downgradeLevel.text", platformVersion)); // NOI18N + + buttonGroup1.add(useOtherPlatform); + org.openide.awt.Mnemonics.setLocalizedText(useOtherPlatform, org.openide.util.NbBundle.getMessage(FixProjectSourceLevel.class, "FixProjectSourceLevel.useOtherPlatform.text")); // NOI18N + + jLabel1.setLabelFor(platforms); + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(FixProjectSourceLevel.class, "FixProjectSourceLevel.jLabel1.text")); // NOI18N + + platforms.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); + + org.openide.awt.Mnemonics.setLocalizedText(managePlatforms, org.openide.util.NbBundle.getMessage(FixProjectSourceLevel.class, "FixProjectSourceLevel.managePlatforms.text")); // NOI18N + managePlatforms.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + managePlatforms(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(FixProjectSourceLevel.class, "FixProjectSourceLevel.jLabel2.text", minVersion)); + + org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .addContainerGap() + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .add(29, 29, 29) + .add(jLabel1) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(platforms, 0, 148, Short.MAX_VALUE) + .add(18, 18, 18) + .add(managePlatforms)) + .add(layout.createSequentialGroup() + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(downgradeLevel) + .add(useOtherPlatform) + .add(jLabel2)) + .add(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .add(jLabel2) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(useOtherPlatform) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE) + .add(jLabel1) + .add(platforms, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE) + .add(managePlatforms)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(downgradeLevel) + .addContainerGap(29, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void managePlatforms(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_managePlatforms + PlatformsCustomizer.showCustomizer(null); + ((PlatformModel)platforms.getModel()).refresh(); + }//GEN-LAST:event_managePlatforms + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.ButtonGroup buttonGroup1; + private javax.swing.JRadioButton downgradeLevel; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel2; + private javax.swing.JButton managePlatforms; + private javax.swing.JComboBox platforms; + private javax.swing.JRadioButton useOtherPlatform; + // End of variables declaration//GEN-END:variables + + private void postInit( + @NonNull String type, + @NonNull SpecificationVersion minVersion) { + useOtherPlatform.setSelected(true); + platforms.setModel(new PlatformModel(type, minVersion)); + platforms.setRenderer(new PlatformRenderer()); + } + + + private static final class PlatformModel extends DefaultComboBoxModel { + + private final String type; + private final SpecificationVersion minVersion; + + + PlatformModel( + @NonNull final String type, + @NonNull final SpecificationVersion minVersion) { + this.type = type; + this.minVersion = minVersion; + refresh(); + } + + + void refresh() { + removeAllElements(); + final JavaPlatform[] platforms = JavaPlatformManager.getDefault().getPlatforms( + null, + new Specification(type, null)); + for (JavaPlatform jp : platforms) { + if (minVersion.compareTo(jp.getSpecification().getVersion())<=0) { + addElement(jp); + } + } + } + } + + private static class PlatformRenderer extends DefaultListCellRenderer { + + @Override + public Component getListCellRendererComponent( + final JList list, + Object value, + final int index, + final boolean isSelected, + final boolean cellHasFocus) { + if (value instanceof JavaPlatform) { + value = ((JavaPlatform)value).getDisplayName(); + } + return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + } + + } +} + --- a/java.project/src/org/netbeans/modules/java/project/ProjectProblemsProviders.java +++ a/java.project/src/org/netbeans/modules/java/project/ProjectProblemsProviders.java @@ -0,0 +1,1361 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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]" + * + * 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. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.java.project; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.File; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.net.URI; +import java.net.URL; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.RunnableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.JFileChooser; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.api.java.platform.JavaPlatform; +import org.netbeans.api.java.platform.JavaPlatformManager; +import org.netbeans.api.java.platform.PlatformsCustomizer; +import org.netbeans.api.java.platform.Specification; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectManager; +import org.netbeans.api.project.ProjectUtils; +import org.netbeans.api.project.libraries.LibrariesCustomizer; +import org.netbeans.api.project.libraries.Library; +import org.netbeans.api.project.libraries.LibraryManager; +import org.netbeans.spi.project.libraries.support.LibrariesSupport; +import org.netbeans.spi.project.support.ant.AntProjectHelper; +import org.netbeans.spi.project.support.ant.EditableProperties; +import org.netbeans.spi.project.support.ant.PropertyEvaluator; +import org.netbeans.spi.project.support.ant.PropertyUtils; +import org.netbeans.spi.project.support.ant.ReferenceHelper; +import org.netbeans.spi.project.ui.ProjectProblemsProvider; +import org.openide.filesystems.FileObject; +import org.openide.util.NbBundle; +import org.openide.util.Parameters; + +import static org.netbeans.modules.java.project.Bundle.*; +import org.netbeans.spi.java.project.support.ui.BrokenReferencesSupport; +import org.netbeans.spi.project.support.ant.ui.VariablesSupport; +import org.netbeans.spi.project.ui.ProjectProblemResolver; +import org.netbeans.spi.project.ui.ProjectProblemsProvider.Result; +import org.netbeans.spi.project.ui.support.ProjectChooser; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.modules.SpecificationVersion; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; +import org.openide.util.Mutex; +import org.openide.util.RequestProcessor; +import org.openide.util.Utilities; +import org.openide.util.WeakListeners; + +/** + * + * @author Tomas Zezula + */ +public class ProjectProblemsProviders { + + private static final Logger LOG = Logger.getLogger(ProjectProblemsProviders.class.getName()); + private static final RequestProcessor RP = new RequestProcessor(ProjectProblemsProviders.class); + + private ProjectProblemsProviders() { + throw new IllegalStateException(String.format("The %s cannot be instantiated.",this.getClass().getName())); //NOI18N + } + + + + public static ProjectProblemsProvider createReferenceProblemProvider( + @NonNull final AntProjectHelper projectHelper, + @NonNull final ReferenceHelper referenceHelper, + @NonNull final PropertyEvaluator evaluator, + @NonNull final String[] properties, + @NonNull final String[] platformProperties) { + final ReferenceProblemProviderImpl pp = new ReferenceProblemProviderImpl(projectHelper, evaluator, referenceHelper, properties, platformProperties); + pp.attachListeners(); + return pp; + } + + public static ProjectProblemsProvider createPlatformVersionProblemProvider( + @NonNull final AntProjectHelper helper, + @NonNull final PropertyEvaluator evaluator, + @NullAllowed final Runnable hook, + @NonNull final String platformType, + @NonNull final String platformProperty, + @NonNull final String... platformVersionProperties) { + final PlatformVersionProblemProviderImpl pp = new PlatformVersionProblemProviderImpl( + helper, + evaluator, + hook, + platformType, + platformProperty, + platformVersionProperties); + pp.attachListeners(); + return pp; + } + + // + @NonNull + private static Set getReferenceProblems( + @NullAllowed final AntProjectHelper helper, + @NullAllowed final PropertyEvaluator evaluator, + @NullAllowed final ReferenceHelper refHelper, + @NonNull final String[] ps, + final boolean abortAfterFirstProblem) { + Set set = new LinkedHashSet(); + StringBuilder all = new StringBuilder(); + // this call waits for list of libraries to be refreshhed + LibraryManager.getDefault().getLibraries(); + if (helper == null || evaluator == null || refHelper == null) { + return set; + } + final Queue fileReoslvers = new ArrayDeque(); + EditableProperties ep = helper.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH); + for (String p : ps) { + // evaluate given property and tokenize it + + String prop = evaluator.getProperty(p); + if (prop == null) { + continue; + } + LOG.log(Level.FINE, "Evaluated {0}={1}", new Object[] {p, prop}); + String[] vals = PropertyUtils.tokenizePath(prop); + + // no check whether after evaluating there are still some + // references which could not be evaluated + for (String v : vals) { + // we are checking only: project reference, file reference, library reference + if (!(v.startsWith("${file.reference.") || v.startsWith("${project.") || v.startsWith("${libs.") || v.startsWith("${var."))) { // NOI18N + all.append(v); + continue; + } + if (v.startsWith("${project.")) { // NOI18N + // something in the form: "${project.}/dist/foo.jar" + String val = v.substring(2, v.indexOf('}')); // NOI18N + set.add( + ProjectProblemsProvider.ProjectProblem.createError( + getDisplayName(RefType.PROJECT, val), + getDescription(RefType.PROJECT, val), + new ProjectResolver(val, helper))); + } else { + final String val = v.substring(2, v.length() - 1); + final ProjectProblemsProvider.ProjectProblem problem; + if (v.startsWith("${file.reference")) { // NOI18N + final FileResolver fr = new FileResolver(val, helper, fileReoslvers); + fileReoslvers.offer(fr); + problem = ProjectProblemsProvider.ProjectProblem.createError( + getDisplayName(RefType.FILE, val), + getDescription(RefType.FILE, val), + fr); + + } else if (v.startsWith("${var")) { // NOI18N + problem = ProjectProblemsProvider.ProjectProblem.createError( + getDisplayName(RefType.VARIABLE, val), + getDescription(RefType.VARIABLE, val), + new VariableResolver(RefType.VARIABLE, val)); + } else { + problem = ProjectProblemsProvider.ProjectProblem.createError( + getDisplayName(RefType.LIBRARY, val), + getDescription(RefType.LIBRARY, val), + new LibraryResolver(RefType.LIBRARY, val, refHelper)); + } + set.add(problem); + } + if (abortAfterFirstProblem) { + break; + } + } + if (set.size() > 0 && abortAfterFirstProblem) { + break; + } + + // test that resolved variable based property points to an existing file + String path = ep.getProperty(p); + if (path != null) { + for (String v : PropertyUtils.tokenizePath(path)) { + if (v.startsWith("${file.reference.")) { //NOI18N + v = ep.getProperty(v.substring(2, v.length() - 1)); + } + if (v != null && v.startsWith("${var.")) { //NOI18N + String value = evaluator.evaluate(v); + if (value.startsWith("${var.")) { // NOI18N + // this problem was already reported + continue; + } + File f = getFile(helper, evaluator, value); + if (f.exists()) { + continue; + } + set.add( + ProjectProblemsProvider.ProjectProblem.createError( + getDisplayName(RefType.VARIABLE_CONTENT, v), + getDescription(RefType.VARIABLE_CONTENT, v), + new VariableResolver(RefType.VARIABLE_CONTENT, v))); + } + } + } + + } + + // Check also that all referenced project really exist and are reachable. + // If they are not report them as broken reference. + // XXX: there will be API in PropertyUtils for listing of Ant + // prop names in String. Consider using it here. + final Map entries = evaluator.getProperties(); + if (entries == null) { + throw new IllegalArgumentException("Properies mapping could not be computed (e.g. due to a circular definition). Evaluator: "+evaluator.toString()); //NOI18N + } + for (Map.Entry entry : entries.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + if (key.startsWith("project.")) { // NOI18N + if ("project.license".equals(key)) { //NOI18N + continue; + } + File f = getFile(helper, evaluator, value); + if (f.exists()) { + continue; + } + // Check that the value is really used by some property. + // If it is not then ignore such a project. + if (all.indexOf(value) == -1) { + continue; + } + set.add( + ProjectProblemsProvider.ProjectProblem.createError( + getDisplayName(RefType.PROJECT, key), + getDescription(RefType.PROJECT, key), + new ProjectResolver(key, helper))); + } + else if (key.startsWith("file.reference")) { //NOI18N + File f = getFile(helper, evaluator, value); + String unevaluatedValue = ep.getProperty(key); + boolean alreadyChecked = unevaluatedValue != null ? unevaluatedValue.startsWith("${var.") : false; // NOI18N + if (f.exists() || all.indexOf(value) == -1 || alreadyChecked) { // NOI18N + continue; + } + final FileResolver fr = new FileResolver(key, helper, fileReoslvers); + fileReoslvers.offer(fr); + set.add( + ProjectProblemsProvider.ProjectProblem.createError( + getDisplayName(RefType.FILE, key), + getDescription(RefType.FILE, key), + fr)); + } + } + + //Check for libbraries with broken classpath content + Set usedLibraries = new HashSet(); + Pattern libPattern = Pattern.compile("\\$\\{(libs\\.[-._a-zA-Z0-9]+\\.classpath)\\}"); //NOI18N + for (String p : ps) { + String propertyValue = ep.getProperty(p); + if (propertyValue != null) { + for (String v : PropertyUtils.tokenizePath(propertyValue)) { + Matcher m = libPattern.matcher(v); + if (m.matches()) { + usedLibraries.add (m.group(1)); + } + } + } + } + for (String libraryRef : usedLibraries) { + String libraryName = libraryRef.substring(5,libraryRef.length()-10); + Library lib = refHelper.findLibrary(libraryName); + if (lib == null) { + // Should already have been caught before? + set.add( + ProjectProblemsProvider.ProjectProblem.createError( + getDisplayName(RefType.LIBRARY, libraryRef), + getDescription(RefType.LIBRARY, libraryRef), + new LibraryResolver(RefType.LIBRARY, libraryRef, refHelper))); + } + else { + //XXX: Should check all the volumes (sources, javadoc, ...)? + for (URI uri : lib.getURIContent("classpath")) { // NOI18N + URI uri2 = LibrariesSupport.getArchiveFile(uri); + if (uri2 == null) { + uri2 = uri; + } + FileObject fo = LibrariesSupport.resolveLibraryEntryFileObject(lib.getManager().getLocation(), uri2); + if (null == fo && !canResolveEvaluatedUri(helper.getStandardPropertyEvaluator(), lib.getManager().getLocation(), uri2)) { + set.add( + ProjectProblemsProvider.ProjectProblem.createError( + getDisplayName(RefType.LIBRARY_CONTENT, libraryRef), + getDescription(RefType.LIBRARY_CONTENT, libraryRef), + new LibraryResolver(RefType.LIBRARY_CONTENT, libraryRef, refHelper))); + break; + } + } + } + } + + return set; + } + + @NonNull + private static Set getPlatformProblems( + @NullAllowed final PropertyEvaluator evaluator, + @NonNull final String[] platformProperties, + boolean abortAfterFirstProblem) { + final Set set = new LinkedHashSet(); + if (evaluator == null) { + return set; + } + for (String pprop : platformProperties) { + String prop = evaluator.getProperty(pprop); + if (prop == null) { + continue; + } + if (!existPlatform(prop)) { + + // XXX: the J2ME stores in project.properties also platform + // display name and so show this display name instead of just + // prop ID if available. + if (evaluator.getProperty(pprop + ".description") != null) { // NOI18N + prop = evaluator.getProperty(pprop + ".description"); // NOI18N + } + + set.add( + ProjectProblemsProvider.ProjectProblem.createError( + getDisplayName(RefType.PLATFORM, prop), + getDescription(RefType.PLATFORM, prop), + new PlatformResolver(prop))); + } + if (set.size() > 0 && abortAfterFirstProblem) { + break; + } + } + return set; + } + + private static File getFile (AntProjectHelper helper, PropertyEvaluator evaluator, String name) { + if (helper != null) { + return new File(helper.resolvePath(name)); + } else { + File f = new File(name); + if (!f.exists()) { + // perhaps the file is relative? + String basedir = evaluator.getProperty("basedir"); // NOI18N + assert basedir != null; + f = new File(new File(basedir), name); + } + return f; + } + } + + /** Tests whether evaluated URI can be resolved. To support library entry + * like "${MAVEN_REPO}/struts/struts.jar". + */ + private static boolean canResolveEvaluatedUri(PropertyEvaluator eval, URL libBase, URI libUri) { + if (libUri.isAbsolute()) { + return false; + } + String path = LibrariesSupport.convertURIToFilePath(libUri); + String newPath = eval.evaluate(path); + if (newPath.equals(path)) { + return false; + } + URI newUri = LibrariesSupport.convertFilePathToURI(newPath); + return null != LibrariesSupport.resolveLibraryEntryFileObject(libBase, newUri); + } + + private static boolean existPlatform(String platform) { + if (platform.equals("default_platform")) { // NOI18N + return true; + } + for (JavaPlatform plat : JavaPlatformManager.getDefault().getInstalledPlatforms()) { + // XXX: this should be defined as PROPERTY somewhere + if (platform.equals(plat.getProperties().get("platform.ant.name")) && // NOI18N + plat.getInstallFolders().size() > 0) { + return true; + } + } + return false; + } + + @NonNull + @NbBundle.Messages({ + "LBL_BrokenLinksCustomizer_BrokenLibrary=\"{0}\" library could not be found", + "LBL_BrokenLinksCustomizer_BrokenDefinableLibrary=\"{0}\" library must be defined", + "LBL_BrokenLinksCustomizer_BrokenLibraryContent=\"{0}\" library has missing items", + "LBL_BrokenLinksCustomizer_BrokenProjectReference=\"{0}\" project could not be found", + "LBL_BrokenLinksCustomizer_BrokenFileReference=\"{0}\" file/folder could not be found", + "LBL_BrokenLinksCustomizer_BrokenVariable=\"{0}\" variable could not be found", + "LBL_BrokenLinksCustomizer_BrokenVariableContent=\"{0}\" variable based file/folder could not be found", + "LBL_BrokenLinksCustomizer_BrokenPlatform=\"{0}\" platform could not be found", + }) + private static String getDisplayName( + @NonNull final RefType type, + @NonNull final String id) { + switch (type) { + case LIBRARY: + return LBL_BrokenLinksCustomizer_BrokenLibrary(getDisplayId(type, id)); + case DEFINABLE_LIBRARY: + return LBL_BrokenLinksCustomizer_BrokenDefinableLibrary(getDisplayId(type, id)); + case LIBRARY_CONTENT: + return LBL_BrokenLinksCustomizer_BrokenLibraryContent(getDisplayId(type, id)); + case PROJECT: + return LBL_BrokenLinksCustomizer_BrokenProjectReference(getDisplayId(type, id)); + case FILE: + return LBL_BrokenLinksCustomizer_BrokenFileReference(getDisplayId(type, id)); + case PLATFORM: + return LBL_BrokenLinksCustomizer_BrokenPlatform(getDisplayId(type, id)); + case VARIABLE: + return LBL_BrokenLinksCustomizer_BrokenVariable(getDisplayId(type, id)); + case VARIABLE_CONTENT: + return LBL_BrokenLinksCustomizer_BrokenVariableContent(getDisplayId(type, id)); + default: + assert false; + return id; + } + } + + @NbBundle.Messages({ + "LBL_BrokenLinksCustomizer_BrokenLibraryDesc=Problem: The project uses a class library called \"{0}\", but this class library was not found.\nSolution: Click Resolve to open the Library Manager and create a new class library called \"{0}\".", + "LBL_BrokenLinksCustomizer_BrokenDefinableLibraryDesc=Problem: The project uses a class library called \"{0}\", but this class library is not currently defined locally.\nSolution: Click Resolve to download or otherwise automatically define this library.", + "LBL_BrokenLinksCustomizer_BrokenLibraryContentDesc=Problem: The project uses the class library called \"{0}\" but the classpath items of this library are missing.\nSolution: Click Resolve to open the Library Manager and locate the missing classpath items of \"{0}\" library.", + "LBL_BrokenLinksCustomizer_BrokenProjectReferenceDesc=Problem: The project classpath includes a reference to the project called \"{0}\", but this project was not found.\nSolution: Click Resolve and locate the missing project.", + "LBL_BrokenLinksCustomizer_BrokenFileReferenceDesc=Problem: The project uses the file/folder called \"{0}\", but this file/folder was not found.\nSolution: Click Resolve and locate the missing file/folder.", + "LBL_BrokenLinksCustomizer_BrokenVariableReferenceDesc=Problem: The project uses the variable called \"{0}\", but this variable was not found.\nSolution: Click Resolve and setup this variable there.", + "LBL_BrokenLinksCustomizer_BrokenVariableContentDesc=Problem: The project uses the variable based file/folder \"{0}\", but this file/folder was not found.\nSolution: Click Resolve and update your variable to point to correct location.", + "LBL_BrokenLinksCustomizer_BrokenPlatformDesc=Problem: The project uses the Java Platform called \"{0}\", but this platform was not found.\nSolution: Click Resolve and create new platform called \"{0}\"." + }) + private static String getDescription( + @NonNull final RefType type, + @NonNull final String id + ) { + switch (type) { + case LIBRARY: + return LBL_BrokenLinksCustomizer_BrokenLibraryDesc(getDisplayId(type, id)); + case DEFINABLE_LIBRARY: + return LBL_BrokenLinksCustomizer_BrokenDefinableLibraryDesc(getDisplayId(type, id)); + case LIBRARY_CONTENT: + return LBL_BrokenLinksCustomizer_BrokenLibraryContentDesc(getDisplayId(type, id)); + case PROJECT: + return LBL_BrokenLinksCustomizer_BrokenProjectReferenceDesc(getDisplayId(type, id)); + case FILE: + return LBL_BrokenLinksCustomizer_BrokenFileReferenceDesc(getDisplayId(type, id)); + case PLATFORM: + return LBL_BrokenLinksCustomizer_BrokenPlatformDesc(getDisplayId(type, id)); + case VARIABLE: + return LBL_BrokenLinksCustomizer_BrokenVariableReferenceDesc(getDisplayId(type, id)); + case VARIABLE_CONTENT: + return LBL_BrokenLinksCustomizer_BrokenVariableContent(getDisplayId(type, id)); + default: + assert false; + return id; + } + } + + private static String getDisplayId( + @NonNull final RefType type, + @NonNull final String id) { + switch (type) { + case LIBRARY: + case DEFINABLE_LIBRARY: + case LIBRARY_CONTENT: + // libs..classpath + return id.substring(5, id.length()-10); + case PROJECT: + // project. + return id.substring(8); + case FILE: + // file.reference. + return id.substring(15); + case PLATFORM: + return id; + case VARIABLE: + return id.substring(4, id.indexOf('}')); // NOI18N + case VARIABLE_CONTENT: + return id.substring(6, id.indexOf('}')) + id.substring(id.indexOf('}')+1); // NOI18N + default: + assert false; + return id; + } + } + + private enum RefType { + PROJECT, + FILE, + PLATFORM, + LIBRARY, + DEFINABLE_LIBRARY, + LIBRARY_CONTENT, + VARIABLE, + VARIABLE_CONTENT, + } + // + + + // + private static abstract class BaseResolver implements ProjectProblemResolver { + + protected final RefType type; + protected final String id; + + BaseResolver( + @NonNull final RefType type, + @NonNull final String id) { + Parameters.notNull("type", type); //NOI18N + Parameters.notNull("id", id); //NOI18N + this.type = type; + this.id = id; + } + + @Override + public final int hashCode() { + int result = 17; + result = 31 * result + type.hashCode(); + result = 31 * result + id.hashCode(); + return result; + } + + @Override + public final boolean equals(@NullAllowed final Object other) { + if (!(other instanceof BaseResolver)) { + return false; + } + final BaseResolver otherResolver = (BaseResolver) other; + return type.equals(otherResolver.type) && + id.equals(otherResolver.id); + } + + @Override + public String toString() { + return String.format( + "Resolver for %s %s", //NOI18N + type, + id); + } + + + + } + + private static class PlatformResolver extends BaseResolver { + + PlatformResolver(@NonNull final String id) { + super(RefType.PLATFORM, id); + } + + @Override + @NonNull + public Future resolve() { + PlatformsCustomizer.showCustomizer(null); + return new Done(ProjectProblemsProvider.Result.create(ProjectProblemsProvider.Status.RESOLVED)); + } + + } + + private static class LibraryResolver extends BaseResolver { + + private final Callable definer; + private final Reference refHelper; + + LibraryResolver( + @NonNull RefType type, + @NonNull final String id, + @NonNull final ReferenceHelper refHelper) { + this(translate(type, id),id, refHelper); + } + + private LibraryResolver( + @NonNull final Object[] typeDefiner/*todo: replace by Pair*/, + @NonNull final String id, + @NonNull final ReferenceHelper refHelper) { + super((RefType)typeDefiner[0],id); + this.definer = (Callable)typeDefiner[1]; + this.refHelper = new WeakReference(refHelper); + } + + @Override + @NonNull + public Future resolve() { + if (type == RefType.DEFINABLE_LIBRARY) { + return resolveByDefiner(); + } else { + return new Done(ProjectProblemsProvider.Result.create(resolveByLibraryManager())); + } + } + + private ProjectProblemsProvider.Status resolveByLibraryManager() { + final LibraryManager lm = getProjectLibraryManager(); + if (lm == null) { + //Closed and freed project + return ProjectProblemsProvider.Status.UNRESOLVED; + } + LibrariesCustomizer.showCustomizer(null,lm); + return ProjectProblemsProvider.Status.RESOLVED; + } + + private Future resolveByDefiner() { + assert definer != null; + final RunnableFuture future = + new FutureTask( + new Callable() { + @Override + public ProjectProblemsProvider.Result call() throws Exception { + ProjectProblemsProvider.Status result = ProjectProblemsProvider.Status.UNRESOLVED; + try { + Library lib = definer.call(); + LOG.log(Level.FINE, "found {0}", lib); //NOI18N + result = ProjectProblemsProvider.Status.RESOLVED; + } catch (Exception x) { + LOG.log(Level.INFO, null, x); + result = resolveByLibraryManager(); + } + return ProjectProblemsProvider.Result.create(result); + } + }); + RP.post(future); + return future; + } + + @CheckForNull + private LibraryManager getProjectLibraryManager() { + final ReferenceHelper rh = refHelper.get(); + return rh == null ? + null: + rh.getProjectLibraryManager() != null ? + rh.getProjectLibraryManager(): + LibraryManager.getDefault(); + } + + @NonNull + private static Object[] translate( + @NonNull RefType original, + @NonNull String id) { + Callable _definer = null; + if (original == RefType.LIBRARY) { + final String name = id.substring(5, id.length() - 10); + for (BrokenReferencesSupport.LibraryDefiner ld : Lookup.getDefault().lookupAll(BrokenReferencesSupport.LibraryDefiner.class)) { + _definer = ld.missingLibrary(name); + if (_definer != null) { + return new Object[] {RefType.DEFINABLE_LIBRARY, _definer}; + } + } + } + return new Object[] {original, null}; + } + } + + private static class VariableResolver extends BaseResolver { + VariableResolver(@NonNull final RefType type, @NonNull final String id) { + super(type, id); + } + + @Override + @NonNull + public Future resolve() { + VariablesSupport.showVariablesCustomizer(); + return new Done(ProjectProblemsProvider.Result.create(ProjectProblemsProvider.Status.RESOLVED)); + } + } + + private static abstract class ReferenceResolver extends BaseResolver { + + static File lastSelectedFile; + + private final Reference antProjectHelper; + + ReferenceResolver( + @NonNull final RefType type, + @NonNull final String id, + @NonNull final AntProjectHelper antProjectHelper) { + super (type, id); + this.antProjectHelper = new WeakReference(antProjectHelper); + } + + abstract void updateReference(@NonNull final File file); + + final void updateReferenceImpl(@NonNull final File file) { + final String reference = id; + final AntProjectHelper helper = antProjectHelper.get(); + if (helper == null) { + //Closed and freed project, ignore + return; + } + FileObject myProjDirFO = helper.getProjectDirectory(); + final String propertiesFile = AntProjectHelper.PRIVATE_PROPERTIES_PATH; + final String path = file.getAbsolutePath(); + Project p; + try { + p = ProjectManager.getDefault().findProject(myProjDirFO); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + p = null; + } + final Project proj = p; + ProjectManager.mutex().postWriteRequest(new Runnable() { + public @Override void run() { + EditableProperties props = helper.getProperties(propertiesFile); + if (!path.equals(props.getProperty(reference))) { + props.setProperty(reference, path); + helper.putProperties(propertiesFile, props); + } + + if (proj != null) { + try { + ProjectManager.getDefault().saveProject(proj); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + } + }); + } + } + + private static class ProjectResolver extends ReferenceResolver { + ProjectResolver(@NonNull final String id, @NonNull AntProjectHelper antProjectHelper) { + super (RefType.PROJECT, id, antProjectHelper); + } + + @Override + @NonNull + @NbBundle.Messages({ + "LBL_BrokenLinksCustomizer_Resolve_Project=Browse Project \"{0}\"", + }) + public Future resolve() { + ProjectProblemsProvider.Status result = ProjectProblemsProvider.Status.UNRESOLVED; + final JFileChooser chooser = ProjectChooser.projectChooser(); + chooser.setDialogTitle(LBL_BrokenLinksCustomizer_Resolve_Project(getDisplayId(type, id))); + if (lastSelectedFile != null) { + chooser.setSelectedFile(lastSelectedFile); + } + int option = chooser.showOpenDialog(null); + if (option == JFileChooser.APPROVE_OPTION) { + updateReference(chooser.getSelectedFile()); + lastSelectedFile = chooser.getSelectedFile(); + result = ProjectProblemsProvider.Status.RESOLVED; + } + return new Done(ProjectProblemsProvider.Result.create(result)); + } + + @Override + void updateReference(@NonNull final File file) { + updateReferenceImpl(file); + } + + } + + private static class FileResolver extends ReferenceResolver { + + private final Queue peers; + private ProjectProblemsProvider.Status resolved = + ProjectProblemsProvider.Status.UNRESOLVED; + + FileResolver( + @NonNull final String id, + @NonNull final AntProjectHelper antProjectHelper, + @NonNull final Queue peers) { + super(RefType.FILE, id, antProjectHelper); + this.peers = peers; + } + + @Override + @NonNull + @NbBundle.Messages({ + "LBL_BrokenLinksCustomizer_Resolve_File=Browse \"{0}\"" + }) + public Future resolve() { + final JFileChooser chooser = new JFileChooser(); + chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); + chooser.setDialogTitle(LBL_BrokenLinksCustomizer_Resolve_File(getDisplayId(type, id))); + if (lastSelectedFile != null) { + chooser.setSelectedFile(lastSelectedFile); + } + int option = chooser.showOpenDialog(null); + if (option == JFileChooser.APPROVE_OPTION) { + updateReference(chooser.getSelectedFile()); + lastSelectedFile = chooser.getSelectedFile(); + resolved = ProjectProblemsProvider.Status.RESOLVED; + } + return new Done(ProjectProblemsProvider.Result.create(resolved)); + } + + @Override + void updateReference(@NonNull final File file) { + updateReferenceImpl(file); + final File parentFolder = file.getParentFile(); + for (FileResolver peer : peers) { + if (this != peer && peer.resolved == ProjectProblemsProvider.Status.UNRESOLVED) { + final File f = new File(parentFolder, getDisplayId(type, id)); + if (f.exists()) { + updateReferenceImpl(f); + } + } + } + } + + + } + + private static class SourceTargetResolver implements ProjectProblemResolver { + + private final String type; + private final String platformProp; + private final Collection invalidVersionProps; + private final SpecificationVersion minVersion; + private final SpecificationVersion platformVersion; + private final Reference helperRef; + private final Runnable hook; + + SourceTargetResolver( + @NonNull final AntProjectHelper helper, + @NullAllowed final Runnable hook, + @NonNull final String type, + @NonNull final String platformProp, + @NonNull final Collection invalidVersionProps, + @NonNull final SpecificationVersion minVersion, + @NonNull final SpecificationVersion platformVersion) { + Parameters.notNull("helper", helper); //NOI18N + Parameters.notNull("type", type); //NOI18N + Parameters.notNull("platformProp", platformProp); //NOI18N + Parameters.notNull("invalidVersionProps", invalidVersionProps); //NOI18N + Parameters.notNull("minVersion", minVersion); //NOI18N + Parameters.notNull("platformVersion", platformVersion); //NOI18N + this.helperRef = new WeakReference(helper); + this.hook = hook; + this.type = type; + this.platformProp = platformProp; + this.invalidVersionProps = invalidVersionProps; + this.minVersion = minVersion; + this.platformVersion = platformVersion; + } + + + @NbBundle.Messages({"LBL_ResolveJDKVersion=Resolve Invalid Java Platform Version - \"{0}\" Project"}) + @Override + public Future resolve() { + final AntProjectHelper helper = helperRef.get(); + if (helper != null) { + final Project project = FileOwnerQuery.getOwner(helper.getProjectDirectory()); + final FixProjectSourceLevel changeVersion = new FixProjectSourceLevel(type, minVersion, platformVersion); + final DialogDescriptor dd = new DialogDescriptor(changeVersion, LBL_ResolveJDKVersion(ProjectUtils.getInformation(project).getDisplayName())); + if (DialogDisplayer.getDefault().notify(dd) == DialogDescriptor.OK_OPTION) { + final Callable resultFnc = + new Callable() { + @Override + public Result call() throws Exception { + if (changeVersion.isDowngradeLevel()) { + final EditableProperties props = helper.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH); + for (String p : invalidVersionProps) { + props.put(p, platformVersion.toString()); + } + helper.putProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH, props); + ProjectManager.getDefault().saveProject(FileOwnerQuery.getOwner(helper.getProjectDirectory())); + return ProjectProblemsProvider.Result.create(ProjectProblemsProvider.Status.RESOLVED); + } else { + final JavaPlatform jp = changeVersion.getSelectedPlatform(); + if (jp != null) { + final String antName = jp.getProperties().get("platform.ant.name"); //NOI18N + if (antName != null) { + final EditableProperties props = helper.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH); + props.setProperty(platformProp, antName); + helper.putProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH, props); + if (hook != null) { + hook.run(); + } + ProjectManager.getDefault().saveProject(project); + return ProjectProblemsProvider.Result.create(ProjectProblemsProvider.Status.RESOLVED); + } + } + } + return ProjectProblemsProvider.Result.create(ProjectProblemsProvider.Status.UNRESOLVED); + } + }; + final RunnableFuture result = new FutureTask(resultFnc); + RP.post(result); + return result; + } + } + return new Done( + Result.create(ProjectProblemsProvider.Status.UNRESOLVED)); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof SourceTargetResolver)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return 17; + + } + + + } + + private static final class Done implements Future { + + private final ProjectProblemsProvider.Result result; + + Done(@NonNull final ProjectProblemsProvider.Result result) { + Parameters.notNull("result", result); //NOI18N + this.result = result; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return true; + } + + @Override + public ProjectProblemsProvider.Result get() throws InterruptedException, ExecutionException { + return result; + } + + @Override + public ProjectProblemsProvider.Result get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return get(); + } + + } + // + + // + private static final class ReferenceProblemProviderImpl implements ProjectProblemsProvider, PropertyChangeListener { + + private final PropertyChangeSupport support = new PropertyChangeSupport(this); + + private final Object problemsLock = new Object(); + //@GuardedBy("problemsLock") + private Collection problems; + //@GuardedBy("problemsLock") + private long eventId; + private final AtomicBoolean listenersInitialized = new AtomicBoolean(); + + private final AntProjectHelper helper; + private final PropertyEvaluator eval; + private final ReferenceHelper refHelper; + private final String[] refProps; + private final String[] platformProps; + + private Map activeLibManLocs; + + + ReferenceProblemProviderImpl( + @NonNull final AntProjectHelper helper, + @NonNull final PropertyEvaluator eval, + @NonNull final ReferenceHelper refHelper, + @NonNull final String[] refProps, + @NonNull final String[] platformProps) { + assert helper != null; + assert eval != null; + assert refHelper != null; + assert refProps != null; + assert platformProps != null; + this.helper = helper; + this.eval = eval; + this.refHelper = refHelper; + this.refProps = Arrays.copyOf(refProps, refProps.length); + this.platformProps = Arrays.copyOf(platformProps, platformProps.length); + } + + @Override + public void addPropertyChangeListener(@NonNull final PropertyChangeListener listener) { + Parameters.notNull("listener", listener); //NOI18N + support.addPropertyChangeListener(listener); + } + + @Override + public void removePropertyChangeListener(@NonNull final PropertyChangeListener listener) { + Parameters.notNull("listener", listener); //NOI18N + support.removePropertyChangeListener(listener); + } + + @Override + public Collection getProblems() { + Collection curProblems; + long curEventId; + synchronized (problemsLock) { + curProblems = problems; + curEventId = eventId; + } + if (curProblems != null) { + return curProblems; + } + curProblems = ProjectManager.mutex().readAccess( + new Mutex.Action>(){ + @Override + public Collection run() { + final Set newProblems = new LinkedHashSet(); + newProblems.addAll(getReferenceProblems(helper,eval,refHelper,refProps,false)); + newProblems.addAll(getPlatformProblems(eval,platformProps,false)); + return Collections.unmodifiableSet(newProblems); + } + }); + synchronized (problemsLock){ + if (curEventId == eventId) { + //No canonical mapping needed + problems = curProblems; + } else if (problems != null) { + curProblems = problems; + } + } + assert curProblems != null; + return curProblems; + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (LibraryManager.PROP_OPEN_LIBRARY_MANAGERS.equals(evt.getPropertyName())) { + addLibraryManagerListener(); + } + synchronized (problemsLock) { + problems = null; + eventId++; + } + support.firePropertyChange(PROP_PROBLEMS,null,null); + } + + void attachListeners() { + if (listenersInitialized.compareAndSet(false, true)) { + eval.addPropertyChangeListener(this); + JavaPlatformManager.getDefault().addPropertyChangeListener(WeakListeners.propertyChange(this, JavaPlatformManager.getDefault())); + LibraryManager.addOpenManagersPropertyChangeListener(new OpenManagersWeakListener(this)); + addLibraryManagerListener(); + } else { + throw new IllegalStateException(); + } + } + + private void addLibraryManagerListener() { + final Map oldLMs; + final boolean attachToDefault; + synchronized (this) { + attachToDefault = activeLibManLocs == null; + if (attachToDefault) { + activeLibManLocs = new HashMap(); + } + oldLMs = new HashMap(activeLibManLocs); + } + if (attachToDefault) { + final LibraryManager manager = LibraryManager.getDefault(); + manager.addPropertyChangeListener(WeakListeners.propertyChange(this, manager)); + } + final Collection managers = LibraryManager.getOpenManagers(); + final Map managerByLocation = new HashMap(); + for (LibraryManager manager : managers) { + final URL url = manager.getLocation(); + if (url != null) { + managerByLocation.put(url, manager); + } + } + final HashMap toRemove = new HashMap(oldLMs); + toRemove.keySet().removeAll(managerByLocation.keySet()); + for (Object[] pair : toRemove.values()) { + ((LibraryManager)pair[0]).removePropertyChangeListener((PropertyChangeListener)pair[1]); + } + managerByLocation.keySet().removeAll(oldLMs.keySet()); + final HashMap toAdd = new HashMap(); + for (Map.Entry e : managerByLocation.entrySet()) { + final LibraryManager manager = e.getValue(); + final PropertyChangeListener listener = WeakListeners.propertyChange(this, manager); + manager.addPropertyChangeListener(listener); + toAdd.put(e.getKey(), new Object[] {manager, listener}); + } + synchronized (this) { + activeLibManLocs.keySet().removeAll(toRemove.keySet()); + activeLibManLocs.putAll(toAdd); + } + } + + } + + private static final class PlatformVersionProblemProviderImpl implements ProjectProblemsProvider, PropertyChangeListener { + + private final PropertyChangeSupport support = new PropertyChangeSupport(this); + private final Object problemsLock = new Object(); + //@GuardedBy("problemsLock") + private Collection problems; + //@GuardedBy("problemsLock") + private long eventId; + private final AtomicBoolean listenersInitialized = new AtomicBoolean(); + + private final AntProjectHelper helper; + private final PropertyEvaluator eval; + private final Runnable hook; + private final String platformType; + private final String platformProp; + private final Set versionProps; + + PlatformVersionProblemProviderImpl( + @NonNull final AntProjectHelper helper, + @NonNull final PropertyEvaluator eval, + @NullAllowed final Runnable hook, + @NonNull final String platformType, + @NonNull final String platformProp, + @NonNull final String... versionProps) { + assert helper != null; + assert eval != null; + assert platformType != null; + assert platformProp != null; + assert versionProps != null; + this.helper = helper; + this.eval = eval; + this.hook = hook; + this.platformType = platformType; + this.platformProp = platformProp; + this.versionProps = new HashSet(Arrays.asList(versionProps)); + } + + @Override + public void addPropertyChangeListener(@NonNull final PropertyChangeListener listener) { + Parameters.notNull("listener", listener); //NOI18N + support.addPropertyChangeListener(listener); + } + + @Override + public void removePropertyChangeListener(@NonNull final PropertyChangeListener listener) { + Parameters.notNull("listener", listener); //NOI18N + support.removePropertyChangeListener(listener); + } + + @Override + @NbBundle.Messages({ + "LBL_Invalid_JDK_Version=Invalid Java Platform Version", + "HINT_Invalid_JDK_Vernsion=The active project platform is an older version than it's required by project source/binary format." + }) + public Collection getProblems() { + Collection curProblems; + long curEventId; + synchronized (problemsLock) { + curProblems = problems; + curEventId = eventId; + } + if (curProblems != null) { + return curProblems; + } + curProblems = ProjectManager.mutex().readAccess( + new Mutex.Action>() { + @Override + public Collection run() { + final JavaPlatform activePlatform = getActivePlatform(); + final SpecificationVersion platformVersion = activePlatform == null ? + null: + activePlatform.getSpecification().getVersion(); + final Collection invalidVersionProps = new ArrayList(versionProps.size()); + SpecificationVersion minVersion = getInvalidJdkVersion( + platformVersion, + invalidVersionProps); + return minVersion != null ? + Collections.singleton(ProjectProblem.createError( + LBL_Invalid_JDK_Version(), + HINT_Invalid_JDK_Vernsion(), + new SourceTargetResolver( + helper, + hook, + platformType, + platformProp, + invalidVersionProps, + minVersion, + platformVersion))) : + Collections.emptySet(); + } + }); + synchronized (problemsLock) { + if (curEventId == eventId) { + //No canonical mapping needed + problems = curProblems; + } else if (problems != null) { + curProblems = problems; + } + } + assert curProblems != null; + return curProblems; + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + final String propName = evt.getPropertyName(); + if (propName == null || platformProp.equals(propName) || versionProps.contains(propName)) { + synchronized (problemsLock) { + problems = null; + eventId++; + } + support.firePropertyChange(PROP_PROBLEMS,null,null); + } + } + + void attachListeners() { + if (listenersInitialized.compareAndSet(false, true)) { + eval.addPropertyChangeListener(this); + } else { + throw new IllegalStateException(); + } + } + + /** + * Gets minimal required JDK version or null if the project platform + * satisfy the required JDK versions. + * @return The minimal {@link SpecificationVersion} of platform or null. + */ + @CheckForNull + private SpecificationVersion getInvalidJdkVersion ( + @NullAllowed final SpecificationVersion platformVersion, + @NonNull final Collection invalidVersionProps) { + SpecificationVersion minVersion = null; + if (platformVersion != null) { + for (String vp : versionProps) { + final String value = this.eval.getProperty(vp); + if (value == null || value.isEmpty()) { + continue; + } + try { + final SpecificationVersion vpVersion = new SpecificationVersion (value); + if (vpVersion.compareTo(platformVersion) > 0) { + invalidVersionProps.add(vp); + minVersion = max(minVersion,vpVersion); + } + } catch (NumberFormatException nfe) { + LOG.log( + Level.WARNING, + "Property: {0} holds non valid version: {1}", //NOI18N + new Object[]{ + vp, + value + }); + } + } + } + return minVersion; + } + + @CheckForNull + private SpecificationVersion max ( + @NullAllowed final SpecificationVersion a, + @NullAllowed final SpecificationVersion b) { + if (a == null) { + return b; + } else if (b == null) { + return a; + } else if (a.compareTo(b)>=0) { + return a; + } else { + return b; + } + } + + @CheckForNull + private JavaPlatform getActivePlatform() { + final String activePlatformId = this.eval.getProperty(platformProp); + final JavaPlatformManager pm = JavaPlatformManager.getDefault(); + if (activePlatformId == null) { + return pm.getDefaultPlatform(); + } + final JavaPlatform[] installedPlatforms = pm.getPlatforms( + null, + new Specification(platformType, null)); + for (JavaPlatform javaPlatform : installedPlatforms) { + final String antName = javaPlatform.getProperties().get("platform.ant.name"); //NOI18N + if (activePlatformId.equals(antName)) { + return javaPlatform; + } + } + return null; + } + } + + private static class OpenManagersWeakListener extends WeakReference implements Runnable, PropertyChangeListener { + + public OpenManagersWeakListener(final PropertyChangeListener listener) { + super(listener, Utilities.activeReferenceQueue()); + } + + @Override + public void run() { + LibraryManager.removeOpenManagersPropertyChangeListener(this); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + final PropertyChangeListener listener = get(); + if (listener != null) { + listener.propertyChange(evt); + } + } + + } + // +} --- a/java.project/src/org/netbeans/spi/java/project/support/ui/BrokenReferencesSupport.java +++ a/java.project/src/org/netbeans/spi/java/project/support/ui/BrokenReferencesSupport.java @@ -44,34 +44,25 @@ package org.netbeans.spi.java.project.support.ui; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import java.awt.Dialog; -import java.io.IOException; import java.util.concurrent.Callable; -import javax.swing.JButton; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.annotations.common.NonNull; import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectManager; -import org.netbeans.api.project.ProjectUtils; import org.netbeans.api.project.libraries.Library; -import org.netbeans.modules.java.project.BrokenReferencesAlertPanel; -import org.netbeans.modules.java.project.BrokenReferencesCustomizer; -import org.netbeans.modules.java.project.BrokenReferencesModel; -import org.netbeans.modules.java.project.JavaProjectSettings; +import org.netbeans.modules.java.project.ProjectProblemsProviders; import org.netbeans.spi.project.support.ant.AntProjectHelper; import org.netbeans.spi.project.support.ant.PropertyEvaluator; import org.netbeans.spi.project.support.ant.ReferenceHelper; -import org.openide.DialogDescriptor; -import org.openide.DialogDisplayer; -import org.openide.util.Exceptions; -import org.openide.util.NbBundle.Messages; import org.openide.util.Parameters; -import org.openide.util.RequestProcessor; -import org.openide.windows.WindowManager; -import static org.netbeans.spi.java.project.support.ui.Bundle.*; +import org.netbeans.spi.project.ui.ProjectProblemsProvider; +import org.netbeans.api.project.ui.ProjectProblems; +import org.openide.filesystems.FileObject; +import org.openide.util.Lookup; +import org.openide.util.lookup.Lookups; +import org.openide.util.lookup.ProxyLookup; /** * Support for managing broken project references. Project freshly checkout from @@ -86,21 +77,10 @@ * customizer. * * @author David Konecny + * @author Tomas Zezula */ public class BrokenReferencesSupport { - - private static final RequestProcessor RP = new RequestProcessor(BrokenReferencesSupport.class); - - private static final boolean suppressBrokenRefAlert = Boolean.getBoolean("BrokenReferencesSupport.suppressBrokenRefAlert"); //NOI18N - - /** Is Broken References alert shown now? */ - private static BrokenReferencesModel.Context context; - - private static RequestProcessor.Task rpTask; - - /** Timeout within which request to show alert will be ignored. */ - private static int BROKEN_ALERT_TIMEOUT = 1000; - + private BrokenReferencesSupport() {} /** @@ -118,15 +98,25 @@ * always exists. * @return true if some problem was found and it is necessary to give * user a chance to fix them + * + * @deprecated Add {@link ProjectProblemsProvider} into project lookup, + * use {@link BrokenReferencesSupport#createReferenceProblemsProvider} as default + * implementation, and use {@link ProjectProblems#isBroken} */ + @Deprecated public static boolean isBroken(AntProjectHelper projectHelper, ReferenceHelper referenceHelper, String[] properties, String[] platformProperties) { Parameters.notNull("projectHelper", projectHelper); //NOI18N Parameters.notNull("referenceHelper", referenceHelper); //NOI18N Parameters.notNull("properties", properties); //NOI18N Parameters.notNull("platformProperties", platformProperties); //NOI18N - return BrokenReferencesModel.isBroken(projectHelper, referenceHelper, - projectHelper.getStandardPropertyEvaluator(), properties, platformProperties); + return ProjectProblems.isBroken(ProjectDecorator.create( + projectHelper, + referenceHelper, + projectHelper.getStandardPropertyEvaluator(), + properties, + platformProperties, + true)); } /** @@ -143,39 +133,21 @@ * platform is expected to be "default_platform" and this platform * always exists. * @see LibraryDefiner + * + * @deprecated Add {@link ProjectProblemsProvider} into project lookup, + * use {@link BrokenReferencesSupport#createReferenceProblemsProvider} as default + * implementation, and use {@link ProjectProblems#showCustomizer} */ - @Messages({ - "LBL_BrokenLinksCustomizer_Close=Close", - "ACSD_BrokenLinksCustomizer_Close=N/A", - "LBL_BrokenLinksCustomizer_Title=Resolve Reference Problems - \"{0}\" Project" - }) + @Deprecated public static void showCustomizer(AntProjectHelper projectHelper, ReferenceHelper referenceHelper, String[] properties, String[] platformProperties) { - BrokenReferencesModel model = new BrokenReferencesModel(projectHelper, referenceHelper, properties, platformProperties); - BrokenReferencesCustomizer customizer = new BrokenReferencesCustomizer(model); - JButton close = new JButton (LBL_BrokenLinksCustomizer_Close()); // NOI18N - close.getAccessibleContext ().setAccessibleDescription (ACSD_BrokenLinksCustomizer_Close()); // NOI18N - String projectDisplayName = "???"; // NOI18N - try { - Project project = ProjectManager.getDefault().findProject(projectHelper.getProjectDirectory()); - if (project != null) { - projectDisplayName = ProjectUtils.getInformation(project).getDisplayName(); - } - } catch (IOException e) { - Exceptions.printStackTrace(e); - } - DialogDescriptor dd = new DialogDescriptor(customizer, - LBL_BrokenLinksCustomizer_Title(projectDisplayName), // NOI18N - true, new Object[] {close}, close, DialogDescriptor.DEFAULT_ALIGN, null, null); - Dialog dlg = null; - try { - dlg = DialogDisplayer.getDefault().createDialog(dd); - dlg.setVisible(true); - } finally { - if (dlg != null) { - dlg.dispose(); - } - } + ProjectProblems.showCustomizer(ProjectDecorator.create( + projectHelper, + referenceHelper, + projectHelper.getStandardPropertyEvaluator(), + properties, + platformProperties, + false)); } /** @@ -184,9 +156,14 @@ * the project opening, and it will take care about showing message box only * once for several subsequent calls during a timeout. * The alert box has also "show this warning again" check box. + * + * @deprecated Add {@link ProjectProblemsProvider} into project lookup, + * use {@link BrokenReferencesSupport#createReferenceProblemsProvider} as default + * implementation, and use {@link ProjectProblems#showAlert} */ + @Deprecated public static void showAlert() { - showAlertImpl(null); + ProjectProblems.showCustomizer(ProjectDecorator.create()); } /** @@ -209,8 +186,12 @@ * platform is expected to be "default_platform" and this platform * always exists. * @since 1.37 + * + * @deprecated Add {@link ProjectProblemsProvider} into project lookup, + * use {@link BrokenReferencesSupport#createReferenceProblemsProvider} as default + * implementation, and use {@link ProjectProblems#showAlert} */ - + @Deprecated public static void showAlert( @NonNull final AntProjectHelper projectHelper, @NonNull final ReferenceHelper referenceHelper, @@ -222,99 +203,90 @@ Parameters.notNull("evaluator", evaluator); //NOI18N Parameters.notNull("properties", properties); //NOI18N Parameters.notNull("platformProperties", platformProperties); //NOI18N - showAlertImpl(new BrokenReferencesModel.BrokenProject(projectHelper, referenceHelper, evaluator, properties, platformProperties)); + ProjectProblems.showAlert(ProjectDecorator.create( + projectHelper, + referenceHelper, + evaluator, + properties, + platformProperties, + false)); } + /** + * Creates a {@link ProjectProblemsProvider} creating broken references + * problems. + * @param projectHelper AntProjectHelper associated with the project. + * @param referenceHelper ReferenceHelper associated with the project. + * @param evaluator the {@link PropertyEvaluator} used to resolve broken references + * @param properties array of property names which values hold + * references which may be broken. For example for J2SE project + * the property names will be: "javac.classpath", "run.classpath", etc. + * @param platformProperties array of property names which values hold + * name of the platform(s) used by the project. These platforms will be + * checked for existence. For example for J2SE project the property + * name is one and it is "platform.active". The name of the default + * platform is expected to be "default_platform" and this platform + * always exists. + * @return the {@link ProjectProblemsProvider} to be laced into project lookup. + * @see ProjectProblemsProvider + * @since 1.48 + */ + public static ProjectProblemsProvider createReferenceProblemsProvider( + @NonNull final AntProjectHelper projectHelper, + @NonNull final ReferenceHelper referenceHelper, + @NonNull final PropertyEvaluator evaluator, + @NonNull final String[] properties, + @NonNull final String[] platformProperties) { + Parameters.notNull("projectHelper", projectHelper); //NOI18N + Parameters.notNull("referenceHelper", referenceHelper); //NOI18N + Parameters.notNull("evaluator", evaluator); //NOI18N + Parameters.notNull("properties", properties); //NOI18N + Parameters.notNull("platformProperties", platformProperties); //NOI18N + return ProjectProblemsProviders.createReferenceProblemProvider( + projectHelper, + referenceHelper, + evaluator, + properties, + platformProperties); + } - @Messages({ - "CTL_Broken_References_Resolve=Resolve Problems...", - "AD_Broken_References_Resolve=N/A", - "CTL_Broken_References_Close=Close", - "AD_Broken_References_Close=N/A", - "MSG_Broken_References_Title=Open Project", - "LBL_Broken_References_Resolve_Panel_Close=Close", - "AD_Broken_References_Resolve_Panel_Close=N/A", - "LBL_Broken_References_Resolve_Panel_Title=Resolve Reference Problems" - }) - private static synchronized void showAlertImpl(@NullAllowed final BrokenReferencesModel.BrokenProject broken) { - if (!JavaProjectSettings.isShowAgainBrokenRefAlert() || suppressBrokenRefAlert) { - return; - } else if (context == null) { - assert rpTask == null; - - final Runnable task = new Runnable() { - public @Override void run() { - final BrokenReferencesModel.Context ctx; - synchronized (BrokenReferencesSupport.class) { - rpTask = null; - ctx = context; - } - if (ctx == null) { - return; - } - try { - final JButton resolveOption = new JButton(CTL_Broken_References_Resolve()); - resolveOption.getAccessibleContext().setAccessibleDescription(AD_Broken_References_Resolve()); - JButton closeOption = new JButton (CTL_Broken_References_Close()); - closeOption.getAccessibleContext().setAccessibleDescription(AD_Broken_References_Close()); - DialogDescriptor dd = new DialogDescriptor(new BrokenReferencesAlertPanel(), - MSG_Broken_References_Title(), - true, - new Object[] {resolveOption, closeOption}, - closeOption, - DialogDescriptor.DEFAULT_ALIGN, - null, - null); - dd.setMessageType(DialogDescriptor.WARNING_MESSAGE); - ctx.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - resolveOption.setVisible(!ctx.isEmpty()); - } - }); - resolveOption.setVisible(!ctx.isEmpty()); - if (DialogDisplayer.getDefault().notify(dd) == resolveOption) { - final BrokenReferencesModel model = new BrokenReferencesModel(ctx, true); - final BrokenReferencesCustomizer customizer = new BrokenReferencesCustomizer(model); - JButton close = new JButton (Bundle.LBL_Broken_References_Resolve_Panel_Close()); - close.getAccessibleContext ().setAccessibleDescription (Bundle.AD_Broken_References_Resolve_Panel_Close()); - dd = new DialogDescriptor(customizer, - Bundle.LBL_Broken_References_Resolve_Panel_Title(), - true, - new Object[] {closeOption}, - closeOption, - DialogDescriptor.DEFAULT_ALIGN, - null, - null); - DialogDisplayer.getDefault().notify(dd); - } - } finally { - synchronized (BrokenReferencesSupport.class) { - //Clean seen references and start from empty list - context = null; - } - } - } - }; - - context = new BrokenReferencesModel.Context(); - rpTask = RP.create(new Runnable() { - @Override - public void run() { - WindowManager.getDefault().invokeWhenUIReady(task); - } - }); - } - - assert context != null; - if (broken != null) { - context.offer(broken); - } - if (rpTask != null) { - //Not yet shown, move - rpTask.schedule(BROKEN_ALERT_TIMEOUT); - } - } + /** + * Creates a {@link ProjectProblemsProvider} creating wrong Java platform + * version problems. + * @param projectHelper AntProjectHelper associated with the project. + * @param evaluator the {@link PropertyEvaluator} used to resolve broken references + * @param postPlatformSetHook called by problem resolution after the platform property has changed + * to a new platform. The project type can do project specific changes like updating project.xml file. + * The hook is called under {@link ProjectManager#mutex} write access before the project is saved. + * @param platformType the type of platform, for example j2se + * @param platformProperty a property holding the active platform id. + * @param versionProperties array of property names which values hold the source, + * target level. + * @return {@link ProjectProblemsProvider} to be laced into project lookup. + * + * @see ProjectProblemsProvider + * @since 1.48 + */ + public static ProjectProblemsProvider createPlatformVersionProblemProvider( + @NonNull final AntProjectHelper projectHelper, + @NonNull final PropertyEvaluator evaluator, + @NullAllowed final Runnable postPlatformSetHook, + @NonNull final String platformType, + @NonNull final String platformProperty, + @NonNull final String... versionProperties) { + Parameters.notNull("projectHelper", projectHelper); //NOI18N + Parameters.notNull("evaluator", evaluator); //NOI18N + Parameters.notNull("platformProperty", platformProperty); //NOI18N + Parameters.notNull("versionProperties", versionProperties); //NOI18N + return ProjectProblemsProviders.createPlatformVersionProblemProvider( + projectHelper, + evaluator, + postPlatformSetHook, + platformType, + platformProperty, + versionProperties); + } + /** * Service which may be {@linkplain ServiceProvider registered} to download remote libraries or otherwise define them. * @since org.netbeans.modules.java.project/1 1.35 @@ -329,5 +301,57 @@ @CheckForNull Callable missingLibrary(String name); } - + + + private static final class ProjectDecorator implements Project { + + private final Project delegate; + private final Lookup lookup; + + private ProjectDecorator( + @NonNull final Project delegate, + @NonNull final ProjectProblemsProvider provider) { + assert delegate != null; + this.delegate = delegate; + this.lookup = new ProxyLookup(delegate.getLookup(),Lookups.singleton(provider)); + } + + private ProjectDecorator() { + this.delegate = null; + this.lookup = Lookup.EMPTY; + } + + @Override + public FileObject getProjectDirectory() { + return delegate.getProjectDirectory(); + } + + @Override + public Lookup getLookup() { + return lookup; + } + + static ProjectDecorator create( + @NonNull final AntProjectHelper projectHelper, + @NonNull final ReferenceHelper referenceHelper, + @NonNull final PropertyEvaluator evaluator, + @NonNull final String[] properties, + @NonNull final String[] platformProperties, + final boolean abortAfterFirstProblem) { + final Project prj = FileOwnerQuery.getOwner(projectHelper.getProjectDirectory()); + return new ProjectDecorator( + prj, + ProjectProblemsProviders.createReferenceProblemProvider( + projectHelper, + referenceHelper, + evaluator, + properties, + platformProperties)); + } + + static ProjectDecorator create() { + return new ProjectDecorator(); + } + } + } --- a/projectui/src/org/netbeans/modules/project/ui/problems/BrokenProjectActionFactory.java +++ a/projectui/src/org/netbeans/modules/project/ui/problems/BrokenProjectActionFactory.java @@ -0,0 +1,109 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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]" + * + * 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. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.project.ui.problems; + +import java.awt.event.ActionEvent; +import java.util.Collection; +import javax.swing.AbstractAction; +import javax.swing.Action; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ui.ProjectProblems; +import org.openide.awt.ActionID; +import org.openide.awt.ActionRegistration; +import org.openide.awt.DynamicMenuContent; +import org.openide.util.ContextAwareAction; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; + +/** + * + * @author Tomas Zezula + */ +@ActionID(id = "org.netbeans.modules.project.ui.problems.BrokenProjectActionFactory", category = "Project") +@ActionRegistration(displayName = "#LBL_Fix_Broken_Links_Action", lazy=false) +public class BrokenProjectActionFactory extends AbstractAction implements ContextAwareAction { + + public BrokenProjectActionFactory() { + putValue(Action.NAME, NbBundle.getMessage(BrokenProjectActionFactory.class, "LBL_Fix_Broken_Links_Action")); + setEnabled(false); + putValue(DynamicMenuContent.HIDE_WHEN_DISABLED, true); + } + + @Override + public void actionPerformed(ActionEvent e) { + throw new IllegalStateException(); + } + + @Override + public Action createContextAwareInstance(Lookup actionContext) { + final Collection p = actionContext.lookupAll(Project.class); + if (p.size() != 1) { + return this; + } + return new BrokenProjectAction(p.iterator().next()); + } + + + /** This action is created only when project has broken references. + * Once these are resolved the action is disabled. + */ + private static class BrokenProjectAction extends AbstractAction { + + private final Project prj; + + BrokenProjectAction(@NonNull final Project prj) { + this.prj = prj; + putValue(Action.NAME, NbBundle.getMessage(BrokenProjectActionFactory.class, "LBL_Fix_Broken_Links_Action")); + setEnabled(ProjectProblems.isBroken(prj)); + putValue(DynamicMenuContent.HIDE_WHEN_DISABLED, true); + } + + @Override + public void actionPerformed(ActionEvent e) { +// helper.requestUpdate(); + ProjectProblems.showCustomizer(prj); + } + + } + +} --- a/projectui/src/org/netbeans/modules/project/ui/problems/BrokenProjectAnnotator.java +++ a/projectui/src/org/netbeans/modules/project/ui/problems/BrokenProjectAnnotator.java @@ -0,0 +1,237 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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]" + * + * 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. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.project.ui.problems; + +import java.awt.Image; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.net.URL; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.event.ChangeListener; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.StaticResource; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ui.OpenProjects; +import org.netbeans.api.project.ui.ProjectProblems; +import org.netbeans.spi.project.ProjectIconAnnotator; +import org.netbeans.spi.project.ui.ProjectProblemsProvider; +import org.openide.util.ChangeSupport; +import org.openide.util.Exceptions; +import org.openide.util.ImageUtilities; +import org.openide.util.NbBundle; +import org.openide.util.Parameters; +import org.openide.util.RequestProcessor; +import org.openide.util.lookup.ServiceProvider; + +/** + * + * @author Tomas Zezula + */ +@ServiceProvider(service=ProjectIconAnnotator.class) +public class BrokenProjectAnnotator implements ProjectIconAnnotator, PropertyChangeListener { + + + @StaticResource + private static final String BROKEN_PROJECT_BADGE_PATH = "org/netbeans/modules/project/ui/resources/brokenProjectBadge.gif"; //NOI18N + private static final URL BROKEN_PROJECT_BADGE_URL = BrokenProjectAnnotator.class.getClassLoader().getResource(BROKEN_PROJECT_BADGE_PATH); + private static final Image BROKEN_PROJECT_BADGE = ImageUtilities.loadImage(BROKEN_PROJECT_BADGE_PATH, true); + private static final int FIRE_DELAY = 500; + private static final RequestProcessor FIRER = new RequestProcessor(BrokenProjectAnnotator.class.getName()+".fire", 1, false, false); //NOI18N + private static final RequestProcessor NOTIFIER = new RequestProcessor(BrokenProjectAnnotator.class.getName()+".alert", 1, false, false); //NOI18N + private static final Logger LOG = Logger.getLogger(BrokenProjectAnnotator.class.getName()); + + private final ChangeSupport changeSupport = new ChangeSupport(this); + private final RequestProcessor.Task task = FIRER.create(new Runnable(){ + @Override + public void run() { + changeSupport.fireChange(); + LOG.fine("Fire."); + } + }); + private final Object cacheLock = new Object(); + //@GuardedBy("cacheLock") + private final Map brokenCache = new WeakHashMap(); + //@GuardedBy("cacheLock") + private final Map>> problemsProvider2prj = + new WeakHashMap>>(); + + + @NonNull + @Override + public Image annotateIcon( + @NonNull final Project project, + @NonNull Image original, + final boolean openedNode) { + LOG.log(Level.FINE, "The annotateIcon called for project: {0}.", project); //NOI18N + Integer problemsCount = null; + boolean firstTime = false; + synchronized (cacheLock) { + if (brokenCache.containsKey(project)) { + problemsCount = brokenCache.get(project); + LOG.log(Level.FINE, "In cache: {0}.", problemsCount); //NOI18N + } else { + firstTime = true; + brokenCache.put(project, problemsCount); + final ProjectProblemsProvider ppp = project.getLookup().lookup(ProjectProblemsProvider.class); + if (ppp != null) { + ppp.addPropertyChangeListener(this); + Set> projects = problemsProvider2prj.get(ppp); + if (projects == null) { + projects = new HashSet>(); + problemsProvider2prj.put(ppp, projects); + } + projects.add(new WeakReference(project)); + } + LOG.fine("Added listeners."); //NOI18N + } + } + + if (problemsCount == null) { + final ProjectProblemsProvider provider = project.getLookup().lookup(ProjectProblemsProvider.class); + final Collection problems = + provider == null? + Collections.emptySet(): + provider.getProblems(); + problemsCount = problems.size(); + if (firstTime) { + for (ProjectProblemsProvider.ProjectProblem p : problems) { + if (p.getSeverity() == ProjectProblemsProvider.Severity.ERROR) { + scheduleAlert(project); + break; + } + } + } + synchronized (cacheLock) { + brokenCache.put(project, problemsCount); + LOG.log(Level.FINE, "Set {0} to cache.", problemsCount); //NOI18N + } + } + if (problemsCount > 0) { + final String message = problemsCount == 1 ? + NbBundle.getMessage(BrokenProjectAnnotator.class, "MSG_OneProblem") : + NbBundle.getMessage(BrokenProjectAnnotator.class, "MSG_MoreProblems", problemsCount); + final String messageHtml = String.format( + " %s", //NOI18N + BROKEN_PROJECT_BADGE_URL.toExternalForm(), + message); + original = ImageUtilities.mergeImages( + original, + ImageUtilities.assignToolTipToImage(BROKEN_PROJECT_BADGE, messageHtml), + 8, + 0); + } + return original; + } + + @Override + public void addChangeListener(@NonNull final ChangeListener listener) { + Parameters.notNull("listener", listener); //NOI18N + changeSupport.addChangeListener(listener); + + } + + @Override + public void removeChangeListener(ChangeListener listener) { + Parameters.notNull("listener", listener); //NOI18N + changeSupport.removeChangeListener(listener); + } + + @Override + public void propertyChange(@NonNull final PropertyChangeEvent evt) { + if (ProjectProblemsProvider.PROP_PROBLEMS.equals(evt.getPropertyName())) { + synchronized (cacheLock) { + final Set> toRefresh = problemsProvider2prj.get(evt.getSource()); + if (toRefresh != null) { + LOG.fine("Event from known ProjectProblemsProvider -> clearing cache for:"); //NOI18N + for (final Iterator> it = toRefresh.iterator();it.hasNext();) { + final Reference ref = it.next(); + final Project prj = ref.get(); + if (prj != null) { + brokenCache.put(prj, null); + LOG.log(Level.FINE, "Project: {0}", prj); //NOI18N + } else { + it.remove(); + } + } + } else { + LOG.fine("Event from unknown ProjectProblemsProvider -> clearing all caches."); //NOI18N + } + } + task.schedule(FIRE_DELAY); + } + } + + /** + * Shows alert for given project. + * Todo: Better would be to use ProjectOpenHook, however there is no way + * how to register the POH for all project types, see issue #215687. + * @param prj + */ + private void scheduleAlert(@NonNull final Project prj) { + NOTIFIER.post(new Runnable() { + @Override + public void run() { + final Future projects = OpenProjects.getDefault().openProjects(); + try { + projects.get(); + } catch (ExecutionException ex) { + Exceptions.printStackTrace(ex); + } catch (InterruptedException ie) { + Exceptions.printStackTrace(ie); + } + ProjectProblems.showAlert(prj); + } + }); + } +} --- a/java.project/src/org/netbeans/modules/java/project/BrokenReferencesAlertPanel.form +++ a/java.project/src/org/netbeans/modules/java/project/BrokenReferencesAlertPanel.form @@ -1,6 +1,6 @@ -
+ @@ -10,7 +10,11 @@ + + + + @@ -22,7 +26,7 @@ - + --- a/java.project/src/org/netbeans/modules/java/project/BrokenReferencesAlertPanel.java +++ a/java.project/src/org/netbeans/modules/java/project/BrokenReferencesAlertPanel.java @@ -42,7 +42,7 @@ * made subject to such option by the copyright holder. */ -package org.netbeans.modules.java.project; +package org.netbeans.modules.project.ui.problems; import javax.swing.JPanel; @@ -50,7 +50,7 @@ public BrokenReferencesAlertPanel() { initComponents(); - notAgain.setSelected(!JavaProjectSettings.isShowAgainBrokenRefAlert()); + notAgain.setSelected(!BrokenReferencesSettings.isShowAgainBrokenRefAlert()); } /** This method is called from within the constructor to @@ -58,7 +58,7 @@ * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ - // //GEN-BEGIN:initComponents + // //GEN-BEGIN:initComponents private void initComponents() { java.awt.GridBagConstraints gridBagConstraints; @@ -69,9 +69,8 @@ setLayout(new java.awt.GridBagLayout()); - getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(BrokenReferencesAlertPanel.class, "ACSN_BrokenReferencesAlertPanel")); - getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(BrokenReferencesAlertPanel.class, "ACSD_BrokenReferencesAlertPanel")); - org.openide.awt.Mnemonics.setLocalizedText(jLabel1, java.util.ResourceBundle.getBundle("org/netbeans/modules/java/project/Bundle").getString("MSG_Broken_References_Label")); + java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("org/netbeans/modules/project/ui/problems/Bundle"); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, bundle.getString("MSG_Broken_References_Label")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 0; @@ -80,14 +79,13 @@ gridBagConstraints.insets = new java.awt.Insets(11, 11, 0, 0); add(jLabel1, gridBagConstraints); - org.openide.awt.Mnemonics.setLocalizedText(notAgain, org.openide.util.NbBundle.getMessage(BrokenReferencesAlertPanel.class, "MSG_BrokenReferencesAlertPanel_notAgain")); + org.openide.awt.Mnemonics.setLocalizedText(notAgain, org.openide.util.NbBundle.getMessage(BrokenReferencesAlertPanel.class, "MSG_BrokenReferencesAlertPanel_notAgain")); // NOI18N notAgain.setMargin(new java.awt.Insets(0, 0, 0, 0)); notAgain.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { notAgainActionPerformed(evt); } }); - gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 2; @@ -95,10 +93,10 @@ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.insets = new java.awt.Insets(6, 11, 0, 0); add(notAgain, gridBagConstraints); - notAgain.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(BrokenReferencesAlertPanel.class, "ACSN_BrokenReferencesAlertPanel_notAgain")); - notAgain.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(BrokenReferencesAlertPanel.class, "ACSD_BrokenReferencesAlertPanel_notAgain")); + notAgain.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(BrokenReferencesAlertPanel.class, "ACSN_BrokenReferencesAlertPanel_notAgain")); // NOI18N + notAgain.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(BrokenReferencesAlertPanel.class, "ACSD_BrokenReferencesAlertPanel_notAgain")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(message, org.openide.util.NbBundle.getMessage(BrokenReferencesAlertPanel.class, "MSG_Broken_References")); + org.openide.awt.Mnemonics.setLocalizedText(message, org.openide.util.NbBundle.getMessage(BrokenReferencesAlertPanel.class, "MSG_Broken_References")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 1; @@ -117,10 +115,12 @@ gridBagConstraints.weighty = 1.0; add(jPanel1, gridBagConstraints); + getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(BrokenReferencesAlertPanel.class, "ACSN_BrokenReferencesAlertPanel")); // NOI18N + getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(BrokenReferencesAlertPanel.class, "ACSD_BrokenReferencesAlertPanel")); // NOI18N }// //GEN-END:initComponents private void notAgainActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_notAgainActionPerformed - JavaProjectSettings.setShowAgainBrokenRefAlert(!notAgain.isSelected()); + BrokenReferencesSettings.setShowAgainBrokenRefAlert(!notAgain.isSelected()); }//GEN-LAST:event_notAgainActionPerformed --- a/java.project/src/org/netbeans/modules/java/project/BrokenReferencesCustomizer.form +++ a/java.project/src/org/netbeans/modules/java/project/BrokenReferencesCustomizer.form @@ -1,4 +1,4 @@ - + --- a/java.project/src/org/netbeans/modules/java/project/BrokenReferencesCustomizer.java +++ a/java.project/src/org/netbeans/modules/java/project/BrokenReferencesCustomizer.java @@ -42,41 +42,39 @@ * made subject to such option by the copyright holder. */ -package org.netbeans.modules.java.project; +package org.netbeans.modules.project.ui.problems; -import org.netbeans.api.project.libraries.Library; -import java.util.logging.Level; -import java.util.logging.Logger; import java.awt.Component; -import java.awt.EventQueue; -import java.io.File; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReference; import javax.swing.DefaultListCellRenderer; -import javax.swing.JFileChooser; import javax.swing.JList; import javax.swing.SwingUtilities; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; import org.netbeans.api.annotations.common.StaticResource; +import org.netbeans.spi.project.ui.ProjectProblemsProvider; +import org.openide.NotificationLineSupport; -import org.netbeans.api.java.platform.PlatformsCustomizer; -import org.netbeans.api.project.libraries.LibrariesCustomizer; -import org.netbeans.api.project.libraries.LibraryManager; -import org.netbeans.spi.project.support.ant.ui.VariablesSupport; -import org.netbeans.spi.project.ui.support.ProjectChooser; +import org.openide.util.Exceptions; import org.openide.util.ImageUtilities; -import static org.netbeans.modules.java.project.Bundle.*; import org.openide.util.NbBundle.Messages; +import org.openide.util.Parameters; import org.openide.util.RequestProcessor; /** * - * @author David Konecny + * @author David Konecny + * @author Tomas Zezula */ public class BrokenReferencesCustomizer extends javax.swing.JPanel { private static final RequestProcessor RP = new RequestProcessor(BrokenReferencesCustomizer.class); - private static final Logger LOG = Logger.getLogger(BrokenReferencesCustomizer.class.getName()); - private BrokenReferencesModel model; - private File lastSelectedFile; + private final BrokenReferencesModel model; + //@GuardedBy("this") + private NotificationLineSupport nls; /** Creates new form BrokenReferencesCustomizer */ public BrokenReferencesCustomizer(BrokenReferencesModel model) { @@ -84,7 +82,7 @@ this.model = model; errorList.setModel(model); errorList.setSelectedIndex(0); - errorList.setCellRenderer(new ListCellRendererImpl(model)); + errorList.setCellRenderer(new ListCellRendererImpl()); } /** This method is called from within the constructor to @@ -92,7 +90,8 @@ * WARNING: Do NOT modify this code. The content of this method is * always regenerated by the Form Editor. */ - private void initComponents() {//GEN-BEGIN:initComponents + // //GEN-BEGIN:initComponents + private void initComponents() { java.awt.GridBagConstraints gridBagConstraints; errorListLabel = new javax.swing.JLabel(); @@ -103,18 +102,16 @@ jScrollPane2 = new javax.swing.JScrollPane(); description = new javax.swing.JTextArea(); + setPreferredSize(new java.awt.Dimension(450, 300)); setLayout(new java.awt.GridBagLayout()); - setPreferredSize(new java.awt.Dimension(450, 300)); - getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "ACSN_BrokenReferencesCustomizer")); - getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "ACSD_BrokenReferencesCustomizer")); errorListLabel.setLabelFor(errorList); - org.openide.awt.Mnemonics.setLocalizedText(errorListLabel, org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "LBL_BrokenLinksCustomizer_List")); + org.openide.awt.Mnemonics.setLocalizedText(errorListLabel, org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "LBL_BrokenLinksCustomizer_List")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST; gridBagConstraints.insets = new java.awt.Insets(6, 12, 3, 0); add(errorListLabel, gridBagConstraints); - errorListLabel.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "ACSD_BrokenLinksCustomizer_List")); + errorListLabel.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "ACSD_BrokenLinksCustomizer_List")); // NOI18N errorList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); errorList.addListSelectionListener(new javax.swing.event.ListSelectionListener() { @@ -122,7 +119,6 @@ errorListValueChanged(evt); } }); - jScrollPane1.setViewportView(errorList); gridBagConstraints = new java.awt.GridBagConstraints(); @@ -135,13 +131,12 @@ gridBagConstraints.insets = new java.awt.Insets(0, 12, 0, 0); add(jScrollPane1, gridBagConstraints); - org.openide.awt.Mnemonics.setLocalizedText(fix, org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "LBL_BrokenLinksCustomizer_Fix")); + org.openide.awt.Mnemonics.setLocalizedText(fix, org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "LBL_BrokenLinksCustomizer_Fix")); // NOI18N fix.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { fixActionPerformed(evt); } }); - gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 1; @@ -149,17 +144,17 @@ gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.insets = new java.awt.Insets(0, 6, 0, 12); add(fix, gridBagConstraints); - fix.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "ACSD_BrokenLinksCustomizer_Fix")); + fix.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "ACSD_BrokenLinksCustomizer_Fix")); // NOI18N descriptionLabel.setLabelFor(description); - org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "LBL_BrokenLinksCustomizer_Description")); + org.openide.awt.Mnemonics.setLocalizedText(descriptionLabel, org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "LBL_BrokenLinksCustomizer_Description")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 2; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.insets = new java.awt.Insets(6, 12, 3, 0); add(descriptionLabel, gridBagConstraints); - descriptionLabel.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "ACSD_BrokenLinksCustomizer_Description")); + descriptionLabel.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "ACSD_BrokenLinksCustomizer_Description")); // NOI18N description.setEditable(false); description.setLineWrap(true); @@ -174,118 +169,90 @@ gridBagConstraints.insets = new java.awt.Insets(0, 12, 5, 0); add(jScrollPane2, gridBagConstraints); - }//GEN-END:initComponents + getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "ACSN_BrokenReferencesCustomizer")); // NOI18N + getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(BrokenReferencesCustomizer.class, "ACSD_BrokenReferencesCustomizer")); // NOI18N + }// //GEN-END:initComponents private void errorListValueChanged(javax.swing.event.ListSelectionEvent evt) {//GEN-FIRST:event_errorListValueChanged updateSelection(); }//GEN-LAST:event_errorListValueChanged - @Messages({ - "LBL_BrokenLinksCustomizer_Resolve_Project=Browse Project \"{0}\"", - "LBL_BrokenLinksCustomizer_Resolve_File=Browse \"{0}\"" - }) + private void fixActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fixActionPerformed - int index = errorList.getSelectedIndex(); - if (index==-1) { + final Object value = errorList.getSelectedValue(); + if (!(value instanceof BrokenReferencesModel.ProblemReference)) { return; } - final BrokenReferencesModel.OneReference or = model.getOneReference(index); - if (or.getType() == BrokenReferencesModel.RefType.LIBRARY || - or.getType() == BrokenReferencesModel.RefType.LIBRARY_CONTENT) { - fix.setEnabled(false); - try { - final LibraryManager lm = model.getProjectLibraryManager(or); - if (lm == null) { - //Closed and freed project - return; + final BrokenReferencesModel.ProblemReference or = (BrokenReferencesModel.ProblemReference) value; + errorList.setEnabled(false); + fix.setEnabled(false); + Future becomesResult = null; + try { + becomesResult = or.problem.resolve(); + assert becomesResult != null; + } finally { + if (becomesResult == null) { + updateAfterResolve(null); + } else if (becomesResult.isDone()) { + ProjectProblemsProvider.Result result = null; + try { + result = becomesResult.get(); + } catch (InterruptedException ex) { + Exceptions.printStackTrace(ex); + } catch (ExecutionException ex) { + Exceptions.printStackTrace(ex); + } finally { + updateAfterResolve(result); } - LibrariesCustomizer.showCustomizer(null,lm); - } finally { - fix.setEnabled(true); - } - } else if (or.getType() == BrokenReferencesModel.RefType.DEFINABLE_LIBRARY) { - fix.setEnabled(false); - RP.post(new Runnable() { - public @Override void run() { - try { - Library lib = or.define(); - LOG.log(Level.FINE, "found {0}", lib); - EventQueue.invokeLater(new Runnable() { - public @Override void run() { - model.refresh(); - updateSelection(); - } - }); - } catch (Exception x) { - LOG.log(Level.INFO, null, x); - // fallback: user may need to create library manually - final LibraryManager lm = model.getProjectLibraryManager(or); - if (lm == null) { - //Closed and freed project - return; + } else { + final Future becomesResultFin = becomesResult; + RP.post(new Runnable() { + @Override + public void run() { + final AtomicReference result = new AtomicReference(); + try { + result.set(becomesResultFin.get()); + } catch (InterruptedException ie) { + Exceptions.printStackTrace(ie); + } catch (ExecutionException ee) { + Exceptions.printStackTrace(ee); + } finally { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + updateAfterResolve(result.get()); + } + }); } - LibrariesCustomizer.showCustomizer(null, lm); - } finally { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - fix.setEnabled(true); - } - }); } - } - }); - return; - } else if (or.getType() == BrokenReferencesModel.RefType.PLATFORM) { - fix.setEnabled(false); - try { - PlatformsCustomizer.showCustomizer(null); - } finally { - fix.setEnabled(true); - } - } else if (or.getType() == BrokenReferencesModel.RefType.VARIABLE || or.getType() == BrokenReferencesModel.RefType.VARIABLE_CONTENT) { - fix.setEnabled(false); - try { - VariablesSupport.showVariablesCustomizer(); - } finally { - fix.setEnabled(true); - } - } else { - fix.setEnabled(false); - try { - JFileChooser chooser; - if (or.getType() == BrokenReferencesModel.RefType.PROJECT) { - chooser = ProjectChooser.projectChooser(); - chooser.setDialogTitle(LBL_BrokenLinksCustomizer_Resolve_Project(or.getDisplayID())); - } else { - chooser = new JFileChooser(); - chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); - chooser.setDialogTitle(LBL_BrokenLinksCustomizer_Resolve_File(or.getDisplayID())); - } - if (lastSelectedFile != null) { - chooser.setSelectedFile(lastSelectedFile); - } - int option = chooser.showOpenDialog(null); - if (option == JFileChooser.APPROVE_OPTION) { - model.updateReference(errorList.getSelectedIndex(), chooser.getSelectedFile()); - lastSelectedFile = chooser.getSelectedFile(); - } - } finally { - fix.setEnabled(true); + }); } } + + }//GEN-LAST:event_fixActionPerformed + + private void updateAfterResolve(@NullAllowed final ProjectProblemsProvider.Result result) { + if (!SwingUtilities.isEventDispatchThread()) { + throw new IllegalStateException(); + } model.refresh(); + errorList.setEnabled(true); updateSelection(); - }//GEN-LAST:event_fixActionPerformed + if (result != null) { + notify(result.getStatus(), result.getMessage()); + } + } @Messages("LBL_BrokenLinksCustomizer_Problem_Was_Resolved=This problem was resolved") private void updateSelection() { - if (errorList.getSelectedIndex() != -1 && errorList.getSelectedIndex() < model.getSize()) { - if (model.isBroken(errorList.getSelectedIndex())) { - description.setText(getDescription(errorList.getSelectedIndex())); + final Object value = errorList.getSelectedValue(); + if (value instanceof BrokenReferencesModel.ProblemReference) { + final BrokenReferencesModel.ProblemReference reference = (BrokenReferencesModel.ProblemReference) value; + if (!reference.resolved) { + description.setText(reference.problem.getDescription()); fix.setEnabled(true); } else { - description.setText(LBL_BrokenLinksCustomizer_Problem_Was_Resolved()); + description.setText(Bundle.LBL_BrokenLinksCustomizer_Problem_Was_Resolved()); // Leave the button always enabled so that user can alter // resolved reference. Especially needed for automatically // resolved JAR references. @@ -295,42 +262,9 @@ description.setText(""); fix.setEnabled(false); } + clearNotification(); } - @Messages({ - "LBL_BrokenLinksCustomizer_BrokenLibraryDesc=Problem: The project uses a class library called \"{0}\", but this class library was not found.\nSolution: Click Resolve to open the Library Manager and create a new class library called \"{0}\".", - "LBL_BrokenLinksCustomizer_BrokenDefinableLibraryDesc=Problem: The project uses a class library called \"{0}\", but this class library is not currently defined locally.\nSolution: Click Resolve to download or otherwise automatically define this library.", - "LBL_BrokenLinksCustomizer_BrokenLibraryContentDesc=Problem: The project uses the class library called \"{0}\" but the classpath items of this library are missing.\nSolution: Click Resolve to open the Library Manager and locate the missing classpath items of \"{0}\" library.", - "LBL_BrokenLinksCustomizer_BrokenProjectReferenceDesc=Problem: The project classpath includes a reference to the project called \"{0}\", but this project was not found.\nSolution: Click Resolve and locate the missing project.", - "LBL_BrokenLinksCustomizer_BrokenFileReferenceDesc=Problem: The project uses the file/folder called \"{0}\", but this file/folder was not found.\nSolution: Click Resolve and locate the missing file/folder.", - "LBL_BrokenLinksCustomizer_BrokenVariableReferenceDesc=Problem: The project uses the variable called \"{0}\", but this variable was not found.\nSolution: Click Resolve and setup this variable there.", - "LBL_BrokenLinksCustomizer_BrokenVariableContentDesc=Problem: The project uses the variable based file/folder \"{0}\", but this file/folder was not found.\nSolution: Click Resolve and update your variable to point to correct location.", - "LBL_BrokenLinksCustomizer_BrokenPlatformDesc=Problem: The project uses the Java Platform called \"{0}\", but this platform was not found.\nSolution: Click Resolve and create new platform called \"{0}\"." - }) - private String getDescription(int index) { - BrokenReferencesModel.OneReference or = model.getOneReference(index); - switch (or.getType()) { - case LIBRARY: - return LBL_BrokenLinksCustomizer_BrokenLibraryDesc(or.getDisplayID()); - case DEFINABLE_LIBRARY: - return LBL_BrokenLinksCustomizer_BrokenDefinableLibraryDesc(or.getDisplayID()); - case LIBRARY_CONTENT: - return LBL_BrokenLinksCustomizer_BrokenLibraryContentDesc(or.getDisplayID()); - case PROJECT: - return LBL_BrokenLinksCustomizer_BrokenProjectReferenceDesc(or.getDisplayID()); - case FILE: - return LBL_BrokenLinksCustomizer_BrokenFileReferenceDesc(or.getDisplayID()); - case VARIABLE: - return LBL_BrokenLinksCustomizer_BrokenVariableReferenceDesc(or.getDisplayID()); - case VARIABLE_CONTENT: - return LBL_BrokenLinksCustomizer_BrokenVariableContentDesc(or.getDisplayID()); - case PLATFORM: - return LBL_BrokenLinksCustomizer_BrokenPlatformDesc(or.getDisplayID()); - default: - assert false; - return null; - } - } // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTextArea description; @@ -342,25 +276,53 @@ private javax.swing.JScrollPane jScrollPane2; // End of variables declaration//GEN-END:variables - private static final @StaticResource String BROKEN_REF = "org/netbeans/modules/java/project/resources/broken-reference.gif"; - private static final @StaticResource String RESOLVED_REF = "org/netbeans/modules/java/project/resources/resolved-reference.gif"; + private static final @StaticResource String BROKEN_REF = "org/netbeans/modules/project/ui/resources/broken-reference.gif"; + private static final @StaticResource String RESOLVED_REF = "org/netbeans/modules/project/ui/resources/resolved-reference.gif"; - private static class ListCellRendererImpl extends DefaultListCellRenderer { + synchronized void setNotificationLineSupport(@NullAllowed final NotificationLineSupport notificationLineSupport) { + Parameters.notNull("notificationLineSupport", notificationLineSupport); //NOI18N + nls = notificationLineSupport; + } - private BrokenReferencesModel model; - - public ListCellRendererImpl(BrokenReferencesModel model) { - this.model = model; + private synchronized void notify ( + @NonNull final ProjectProblemsProvider.Status status, + @NullAllowed final String message) { + if (message != null) { + switch (status) { + case RESOLVED: + nls.setInformationMessage(message); + break; + case RESOLVED_WITH_WARNING: + nls.setWarningMessage(message); + break; + case UNRESOLVED: + nls.setErrorMessage(message); + break; + default: + throw new IllegalStateException(status.name()); + } } + } + + private synchronized void clearNotification() { + //May be null when called from the constructor, before initialization + if (nls != null) { + nls.clearMessages(); + } + } + + private static class ListCellRendererImpl extends DefaultListCellRenderer { public @Override Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus ); - if (model.isBroken(index)) { - setIcon(ImageUtilities.loadImageIcon(BROKEN_REF, false)); - } else { - setIcon(ImageUtilities.loadImageIcon(RESOLVED_REF, false)); + if (value instanceof BrokenReferencesModel.ProblemReference) { + BrokenReferencesModel.ProblemReference problemRef = (BrokenReferencesModel.ProblemReference) value; + super.getListCellRendererComponent( list, problemRef.getDisplayName(), index, isSelected, cellHasFocus ); + if (problemRef.resolved) { + setIcon(ImageUtilities.loadImageIcon(RESOLVED_REF, false)); + } else { + setIcon(ImageUtilities.loadImageIcon(BROKEN_REF, false)); + } } - return this; } } --- a/projectui/src/org/netbeans/modules/project/ui/problems/BrokenReferencesImpl.java +++ a/projectui/src/org/netbeans/modules/project/ui/problems/BrokenReferencesImpl.java @@ -0,0 +1,199 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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]" + * + * 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. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.project.ui.problems; + +import java.awt.Dialog; +import javax.swing.JButton; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ProjectUtils; +import org.netbeans.modules.project.uiapi.BrokenReferencesImplementation; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.util.NbBundle; +import org.openide.util.RequestProcessor; +import org.openide.util.lookup.ServiceProvider; +import org.openide.windows.WindowManager; +import static org.netbeans.modules.project.ui.problems.Bundle.*; +import org.openide.util.Parameters; + +/** + * + * @author Tomas Zezula + */ +@ServiceProvider(service=BrokenReferencesImplementation.class) +public class BrokenReferencesImpl implements BrokenReferencesImplementation { + + private static final boolean suppressBrokenRefAlert = Boolean.getBoolean("BrokenReferencesSupport.suppressBrokenRefAlert"); //NOI18N + private static final RequestProcessor RP = new RequestProcessor(BrokenReferencesImpl.class); + private static int BROKEN_ALERT_TIMEOUT = 1000; + + private BrokenReferencesModel.Context context; + private RequestProcessor.Task rpTask; + + + @Override + @NbBundle.Messages({ + "CTL_Broken_References_Resolve=Resolve Problems...", + "AD_Broken_References_Resolve=N/A", + "CTL_Broken_References_Close=Close", + "AD_Broken_References_Close=N/A", + "MSG_Broken_References_Title=Open Project", + "LBL_Broken_References_Resolve_Panel_Close=Close", + "AD_Broken_References_Resolve_Panel_Close=N/A", + "LBL_Broken_References_Resolve_Panel_Title=Resolve Project Problems" + }) + public void showAlert(Project project) { + Parameters.notNull("project", project); //NOI18N + if (!BrokenReferencesSettings.isShowAgainBrokenRefAlert() || suppressBrokenRefAlert) { + return; + } else if (context == null) { + assert rpTask == null; + + final Runnable task = new Runnable() { + @Override + public void run() { + final BrokenReferencesModel.Context ctx; + synchronized (BrokenReferencesImpl.this) { + rpTask = null; + ctx = context; + } + if (ctx == null) { + return; + } + try { + final JButton resolveOption = new JButton(CTL_Broken_References_Resolve()); + resolveOption.getAccessibleContext().setAccessibleDescription(AD_Broken_References_Resolve()); + JButton closeOption = new JButton (CTL_Broken_References_Close()); + closeOption.getAccessibleContext().setAccessibleDescription(AD_Broken_References_Close()); + DialogDescriptor dd = new DialogDescriptor(new BrokenReferencesAlertPanel(), + MSG_Broken_References_Title(), + true, + new Object[] {resolveOption, closeOption}, + closeOption, + DialogDescriptor.DEFAULT_ALIGN, + null, + null); + dd.setMessageType(DialogDescriptor.WARNING_MESSAGE); + ctx.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + resolveOption.setVisible(!ctx.isEmpty()); + } + }); + resolveOption.setVisible(!ctx.isEmpty()); + if (DialogDisplayer.getDefault().notify(dd) == resolveOption) { + final BrokenReferencesModel model = new BrokenReferencesModel(ctx, true); + final BrokenReferencesCustomizer customizer = new BrokenReferencesCustomizer(model); + JButton close = new JButton (Bundle.LBL_Broken_References_Resolve_Panel_Close()); + close.getAccessibleContext ().setAccessibleDescription (Bundle.AD_Broken_References_Resolve_Panel_Close()); + dd = new DialogDescriptor(customizer, + Bundle.LBL_Broken_References_Resolve_Panel_Title(), + true, + new Object[] {closeOption}, + closeOption, + DialogDescriptor.DEFAULT_ALIGN, + null, + null); + customizer.setNotificationLineSupport(dd.createNotificationLineSupport()); + DialogDisplayer.getDefault().notify(dd); + } + } finally { + synchronized (BrokenReferencesImpl.this) { + //Clean seen references and start from empty list + context = null; + } + } + } + }; + + context = new BrokenReferencesModel.Context(); + rpTask = RP.create(new Runnable() { + @Override + public void run() { + WindowManager.getDefault().invokeWhenUIReady(task); + } + }); + } + + assert context != null; + if (project != null) { + context.offer(project); + } + if (rpTask != null) { + //Not yet shown, move + rpTask.schedule(BROKEN_ALERT_TIMEOUT); + } + } + + + @NbBundle.Messages({ + "LBL_BrokenLinksCustomizer_Close=Close", + "ACSD_BrokenLinksCustomizer_Close=N/A", + "LBL_BrokenLinksCustomizer_Title=Resolve Project Problems - \"{0}\" Project" + }) + @Override + public void showCustomizer(@NonNull Project project) { + Parameters.notNull("project", project); //NOI18N + BrokenReferencesModel model = new BrokenReferencesModel(project); + BrokenReferencesCustomizer customizer = new BrokenReferencesCustomizer(model); + JButton close = new JButton (LBL_BrokenLinksCustomizer_Close()); // NOI18N + close.getAccessibleContext ().setAccessibleDescription (ACSD_BrokenLinksCustomizer_Close()); // NOI18N + String projectDisplayName = ProjectUtils.getInformation(project).getDisplayName(); + DialogDescriptor dd = new DialogDescriptor(customizer, + LBL_BrokenLinksCustomizer_Title(projectDisplayName), // NOI18N + true, new Object[] {close}, close, DialogDescriptor.DEFAULT_ALIGN, null, null); + customizer.setNotificationLineSupport(dd.createNotificationLineSupport()); + Dialog dlg = null; + try { + dlg = DialogDisplayer.getDefault().createDialog(dd); + dlg.setVisible(true); + } finally { + if (dlg != null) { + dlg.dispose(); + } + } + } + +} --- a/java.project/src/org/netbeans/modules/java/project/BrokenReferencesModel.java +++ a/java.project/src/org/netbeans/modules/java/project/BrokenReferencesModel.java @@ -42,53 +42,26 @@ * made subject to such option by the copyright holder. */ -package org.netbeans.modules.java.project; +package org.netbeans.modules.project.ui.problems; -import java.io.File; -import java.io.IOException; -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; -import java.net.URI; -import java.net.URL; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.swing.AbstractListModel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.netbeans.api.annotations.common.NonNull; -import org.netbeans.api.java.platform.JavaPlatform; -import org.netbeans.api.java.platform.JavaPlatformManager; -import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; -import org.netbeans.api.project.ProjectManager; import org.netbeans.api.project.ProjectUtils; -import org.netbeans.api.project.libraries.Library; -import org.netbeans.api.project.libraries.LibraryManager; -import org.netbeans.spi.java.project.support.ui.BrokenReferencesSupport.LibraryDefiner; -import org.netbeans.spi.project.libraries.support.LibrariesSupport; -import org.netbeans.spi.project.support.ant.AntProjectHelper; -import org.netbeans.spi.project.support.ant.EditableProperties; -import org.netbeans.spi.project.support.ant.PropertyEvaluator; -import org.netbeans.spi.project.support.ant.PropertyUtils; -import org.netbeans.spi.project.support.ant.ReferenceHelper; -import org.openide.filesystems.FileObject; import org.openide.util.ChangeSupport; -import org.openide.util.Exceptions; -import org.openide.util.Lookup; -import static org.netbeans.modules.java.project.Bundle.*; -import org.openide.util.NbBundle.Messages; +import org.netbeans.spi.project.ui.ProjectProblemsProvider; +import org.netbeans.spi.project.ui.ProjectProblemsProvider.ProjectProblem; +import org.openide.util.NbBundle; public final class BrokenReferencesModel extends AbstractListModel { @@ -96,18 +69,13 @@ private final Context ctx; private final boolean global; - private List references; + private List problems; - public BrokenReferencesModel(AntProjectHelper helper, - ReferenceHelper resolver, String[] props, String[] platformsProps) { - this(new Context(new BrokenProject(helper, resolver, helper.getStandardPropertyEvaluator(), props, platformsProps)),false); - } - - public BrokenReferencesModel(final @NonNull Context ctx, boolean global) { + BrokenReferencesModel(@NonNull final Context ctx, boolean global) { assert ctx != null; this.ctx = ctx; this.global = global; - references = new ArrayList(); + problems = new ArrayList(); refresh(); ctx.addChangeListener(new ChangeListener() { @Override @@ -117,627 +85,142 @@ }); } - public void refresh() { - Set all = new LinkedHashSet(); - for (BrokenProject bprj : ctx.getBrokenProjects()) { - Set s = getReferences(bprj, false); - all.addAll(s); - s = getPlatforms(bprj, false); - all.addAll(s); + BrokenReferencesModel(@NonNull final Project project) { + this(new Context(), false); + this.ctx.offer(project); + } + + @Override + public Object getElementAt(int index) { + return getOneReference(index); + } + + @Override + public int getSize() { + return problems.size(); + } + + void refresh() { + final Set all = new LinkedHashSet(); + for (Project bprj : ctx.getBrokenProjects()) { + final ProjectProblemsProvider ppp = bprj.getLookup().lookup(ProjectProblemsProvider.class); + if (ppp != null) { + for (ProjectProblem problem : ppp.getProblems()) { + all.add(new ProblemReference(problem, bprj, global)); + } + } } - updateReferencesList(references, all); + updateReferencesList(problems, all); this.fireContentsChanged(this, 0, getSize()); } - @Messages({ - "LBL_BrokenLinksCustomizer_BrokenLibrary=\"{0}\" library could not be found", - "LBL_BrokenLinksCustomizer_BrokenDefinableLibrary=\"{0}\" library must be defined", - "LBL_BrokenLinksCustomizer_BrokenLibraryContent=\"{0}\" library has missing items", - "LBL_BrokenLinksCustomizer_BrokenProjectReference=\"{0}\" project could not be found", - "LBL_BrokenLinksCustomizer_BrokenFileReference=\"{0}\" file/folder could not be found", - "LBL_BrokenLinksCustomizer_BrokenVariable=\"{0}\" variable could not be found", - "LBL_BrokenLinksCustomizer_BrokenVariableContent=\"{0}\" variable based file/folder could not be found", - "LBL_BrokenLinksCustomizer_BrokenPlatform=\"{0}\" platform could not be found", - "LBL_BrokenLinksCustomizer_BrokenLibrary_In_Project=\"{0}\" library (in {1}) could not be found", - "LBL_BrokenLinksCustomizer_BrokenDefinableLibrary_In_Project=\"{0}\" library (in {1}) must be defined", - "LBL_BrokenLinksCustomizer_BrokenLibraryContent_In_Project=\"{0}\" library (in {1}) has missing items", - "LBL_BrokenLinksCustomizer_BrokenProjectReference_In_Project=\"{0}\" project (in {1}) could not be found", - "LBL_BrokenLinksCustomizer_BrokenFileReference_In_Project=\"{0}\" file/folder (in {1}) could not be found", - "LBL_BrokenLinksCustomizer_BrokenVariable_In_Project=\"{0}\" variable (in {1}) could not be found", - "LBL_BrokenLinksCustomizer_BrokenVariableContent_In_Project=\"{0}\" variable based file/folder (in {1}) could not be found", - "LBL_BrokenLinksCustomizer_BrokenPlatform_In_Project=\"{0}\" platform (in {1}) could not be found" - }) - public @Override Object getElementAt(int index) { - final OneReference or = getOneReference(index); - final Project prj = or.bprj.getProject(); - if (global && prj != null) { - final String projectName = ProjectUtils.getInformation(prj).getDisplayName(); - switch (or.type) { - case LIBRARY: - return LBL_BrokenLinksCustomizer_BrokenLibrary_In_Project(or.getDisplayID(), projectName); - case DEFINABLE_LIBRARY: - return LBL_BrokenLinksCustomizer_BrokenDefinableLibrary_In_Project(or.getDisplayID(), projectName); - case LIBRARY_CONTENT: - return LBL_BrokenLinksCustomizer_BrokenLibraryContent_In_Project(or.getDisplayID(), projectName); - case PROJECT: - return LBL_BrokenLinksCustomizer_BrokenProjectReference_In_Project(or.getDisplayID(), projectName); - case FILE: - return LBL_BrokenLinksCustomizer_BrokenFileReference_In_Project(or.getDisplayID(), projectName); - case VARIABLE: - return LBL_BrokenLinksCustomizer_BrokenVariable_In_Project(or.getDisplayID(), projectName); - case VARIABLE_CONTENT: - return LBL_BrokenLinksCustomizer_BrokenVariableContent_In_Project(or.getDisplayID(), projectName); - case PLATFORM: - return LBL_BrokenLinksCustomizer_BrokenPlatform_In_Project(or.getDisplayID(), projectName); - default: - assert false; - return null; - } - } else { - switch (or.type) { - case LIBRARY: - return LBL_BrokenLinksCustomizer_BrokenLibrary(or.getDisplayID()); - case DEFINABLE_LIBRARY: - return LBL_BrokenLinksCustomizer_BrokenDefinableLibrary(or.getDisplayID()); - case LIBRARY_CONTENT: - return LBL_BrokenLinksCustomizer_BrokenLibraryContent(or.getDisplayID()); - case PROJECT: - return LBL_BrokenLinksCustomizer_BrokenProjectReference(or.getDisplayID()); - case FILE: - return LBL_BrokenLinksCustomizer_BrokenFileReference(or.getDisplayID()); - case VARIABLE: - return LBL_BrokenLinksCustomizer_BrokenVariable(or.getDisplayID()); - case VARIABLE_CONTENT: - return LBL_BrokenLinksCustomizer_BrokenVariableContent(or.getDisplayID()); - case PLATFORM: - return LBL_BrokenLinksCustomizer_BrokenPlatform(or.getDisplayID()); - default: - assert false; - return null; - } - } - } - - public OneReference getOneReference(int index) { - assert index>=0 && index=0 && index s = getReferences(bprj, true); - if (s.size() > 0) { - return true; + private static void updateReferencesList(List oldBroken, Set newBroken) { + LOG.log(Level.FINE, "References updated from {0} to {1}", new Object[] {oldBroken, newBroken}); + for (ProblemReference or : oldBroken) { + or.resolved = !newBroken.contains(or); } - s = getPlatforms(bprj, true); - return s.size() > 0; - } - - private static Set getReferences(final BrokenProject bprj, final boolean abortAfterFirstProblem) { - Set set = new LinkedHashSet(); - StringBuilder all = new StringBuilder(); - // this call waits for list of libraries to be refreshhed - LibraryManager.getDefault().getLibraries(); - final AntProjectHelper helper = bprj.getAntProjectHelper(); - final PropertyEvaluator evaluator = bprj.getEvaluator(); - final ReferenceHelper refHelper = bprj.getReferenceHelper(); - if (helper == null || evaluator == null || refHelper == null) { - return set; - } - final String[] ps = bprj.getProperties(); - EditableProperties ep = helper.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH); - for (String p : ps) { - // evaluate given property and tokenize it - - String prop = evaluator.getProperty(p); - if (prop == null) { - continue; - } - LOG.log(Level.FINE, "Evaluated {0}={1}", new Object[] {p, prop}); - String[] vals = PropertyUtils.tokenizePath(prop); - - // no check whether after evaluating there are still some - // references which could not be evaluated - for (String v : vals) { - // we are checking only: project reference, file reference, library reference - if (!(v.startsWith("${file.reference.") || v.startsWith("${project.") || v.startsWith("${libs.") || v.startsWith("${var."))) { // NOI18N - all.append(v); - continue; - } - if (v.startsWith("${project.")) { // NOI18N - // something in the form: "${project.}/dist/foo.jar" - String val = v.substring(2, v.indexOf('}')); // NOI18N - set.add(new OneReference(bprj, RefType.PROJECT, val, true)); - } else { - RefType type = RefType.LIBRARY; - if (v.startsWith("${file.reference")) { // NOI18N - type = RefType.FILE; - } else if (v.startsWith("${var")) { // NOI18N - type = RefType.VARIABLE; - } - String val = v.substring(2, v.length() - 1); - set.add(new OneReference(bprj, type, val, true)); - } - if (abortAfterFirstProblem) { - break; - } - } - if (set.size() > 0 && abortAfterFirstProblem) { - break; - } - - // test that resolved variable based property points to an existing file - String path = ep.getProperty(p); - if (path != null) { - for (String v : PropertyUtils.tokenizePath(path)) { - if (v.startsWith("${file.reference.")) { //NOI18N - v = ep.getProperty(v.substring(2, v.length() - 1)); - } - if (v != null && v.startsWith("${var.")) { //NOI18N - String value = evaluator.evaluate(v); - if (value.startsWith("${var.")) { // NOI18N - // this problem was already reported - continue; - } - File f = getFile(helper, evaluator, value); - if (f.exists()) { - continue; - } - set.add(new OneReference(bprj, RefType.VARIABLE_CONTENT, v, true)); - } - } - } - - } - - // Check also that all referenced project really exist and are reachable. - // If they are not report them as broken reference. - // XXX: there will be API in PropertyUtils for listing of Ant - // prop names in String. Consider using it here. - final Map entries = evaluator.getProperties(); - if (entries == null) { - throw new IllegalArgumentException("Properies mapping could not be computed (e.g. due to a circular definition). Evaluator: "+evaluator.toString()); //NOI18N - } - for (Map.Entry entry : entries.entrySet()) { - String key = entry.getKey(); - String value = entry.getValue(); - if (key.startsWith("project.")) { // NOI18N - if ("project.license".equals(key)) { //NOI18N - continue; - } - File f = getFile(helper, evaluator, value); - if (f.exists()) { - continue; - } - // Check that the value is really used by some property. - // If it is not then ignore such a project. - if (all.indexOf(value) == -1) { - continue; - } - set.add(new OneReference(bprj, RefType.PROJECT, key, true)); - } - else if (key.startsWith("file.reference")) { //NOI18N - File f = getFile(helper, evaluator, value); - String unevaluatedValue = ep.getProperty(key); - boolean alreadyChecked = unevaluatedValue != null ? unevaluatedValue.startsWith("${var.") : false; // NOI18N - if (f.exists() || all.indexOf(value) == -1 || alreadyChecked) { // NOI18N - continue; - } - set.add(new OneReference(bprj, RefType.FILE, key, true)); - } - } - - //Check for libbraries with broken classpath content - Set usedLibraries = new HashSet(); - Pattern libPattern = Pattern.compile("\\$\\{(libs\\.[-._a-zA-Z0-9]+\\.classpath)\\}"); //NOI18N - for (String p : ps) { - String propertyValue = ep.getProperty(p); - if (propertyValue != null) { - for (String v : PropertyUtils.tokenizePath(propertyValue)) { - Matcher m = libPattern.matcher(v); - if (m.matches()) { - usedLibraries.add (m.group(1)); - } - } - } - } - for (String libraryRef : usedLibraries) { - String libraryName = libraryRef.substring(5,libraryRef.length()-10); - Library lib = refHelper.findLibrary(libraryName); - if (lib == null) { - // Should already have been caught before? - set.add(new OneReference(bprj, RefType.LIBRARY, libraryRef, true)); - } - else { - //XXX: Should check all the volumes (sources, javadoc, ...)? - for (URI uri : lib.getURIContent("classpath")) { // NOI18N - URI uri2 = LibrariesSupport.getArchiveFile(uri); - if (uri2 == null) { - uri2 = uri; - } - FileObject fo = LibrariesSupport.resolveLibraryEntryFileObject(lib.getManager().getLocation(), uri2); - if (null == fo && !canResolveEvaluatedUri(helper.getStandardPropertyEvaluator(), lib.getManager().getLocation(), uri2)) { - set.add(new OneReference(bprj, RefType.LIBRARY_CONTENT, libraryRef, true)); - break; - } - } - } - } - - return set; - } - - /** Tests whether evaluated URI can be resolved. To support library entry - * like "${MAVEN_REPO}/struts/struts.jar". - */ - private static boolean canResolveEvaluatedUri(PropertyEvaluator eval, URL libBase, URI libUri) { - if (libUri.isAbsolute()) { - return false; - } - String path = LibrariesSupport.convertURIToFilePath(libUri); - String newPath = eval.evaluate(path); - if (newPath.equals(path)) { - return false; - } - URI newUri = LibrariesSupport.convertFilePathToURI(newPath); - return null != LibrariesSupport.resolveLibraryEntryFileObject(libBase, newUri); - } - - private static File getFile (AntProjectHelper helper, PropertyEvaluator evaluator, String name) { - if (helper != null) { - return new File(helper.resolvePath(name)); - } else { - File f = new File(name); - if (!f.exists()) { - // perhaps the file is relative? - String basedir = evaluator.getProperty("basedir"); // NOI18N - assert basedir != null; - f = new File(new File(basedir), name); - } - return f; - } - } - - private static Set getPlatforms(final BrokenProject bprj, boolean abortAfterFirstProblem) { - final Set set = new LinkedHashSet(); - final PropertyEvaluator evaluator = bprj.getEvaluator(); - if (evaluator == null) { - return set; - } - for (String pprop : bprj.getPlatformProperties()) { - String prop = evaluator.getProperty(pprop); - if (prop == null) { - continue; - } - if (!existPlatform(prop)) { - - // XXX: the J2ME stores in project.properties also platform - // display name and so show this display name instead of just - // prop ID if available. - if (evaluator.getProperty(pprop + ".description") != null) { // NOI18N - prop = evaluator.getProperty(pprop + ".description"); // NOI18N - } - - set.add(new OneReference(bprj, RefType.PLATFORM, prop, true)); - } - if (set.size() > 0 && abortAfterFirstProblem) { - break; - } - } - return set; - } - - private static void updateReferencesList(List oldBroken, Set newBroken) { - LOG.log(Level.FINE, "References updated from {0} to {1}", new Object[] {oldBroken, newBroken}); - for (OneReference or : oldBroken) { - if (newBroken.contains(or)) { - or.broken = true; - } else { - or.broken = false; - } - } - for (OneReference or : newBroken) { + for (ProblemReference or : newBroken) { if (!oldBroken.contains(or)) { oldBroken.add(or); } } } - - private static boolean existPlatform(String platform) { - if (platform.equals("default_platform")) { // NOI18N - return true; - } - for (JavaPlatform plat : JavaPlatformManager.getDefault().getInstalledPlatforms()) { - // XXX: this should be defined as PROPERTY somewhere - if (platform.equals(plat.getProperties().get("platform.ant.name")) && // NOI18N - plat.getInstallFolders().size() > 0) { - return true; - } - } - return false; - } - // XXX: perhaps could be moved to ReferenceResolver. - // But nobody should need it so it is here for now. - void updateReference(int index, File file) { - updateReference0(index, file); - // #48210 - check whether the folder does not contain other jars - // which could auto resolve some broken links: - OneReference or = getOneReference(index); - if (or.getType() != RefType.FILE) { - return; - } - for (int i=0; i definer; - - OneReference( - @NonNull final BrokenProject bprj, - @NonNull RefType type, - @NonNull final String ID, - final boolean broken) { - assert bprj != null; - Callable _definer = null; - if (type == RefType.LIBRARY) { - String name = ID.substring(5, ID.length() - 10); - for (LibraryDefiner ld : Lookup.getDefault().lookupAll(LibraryDefiner.class)) { - _definer = ld.missingLibrary(name); - if (_definer != null) { - type = RefType.DEFINABLE_LIBRARY; - break; - } - } - } - this.bprj = bprj; - this.type = type; - this.ID = ID; - this.broken = broken; - definer = _definer; - } - - public RefType getType() { - return type; - } - - public String getDisplayID() { - switch (type) { - - case LIBRARY: - case DEFINABLE_LIBRARY: - case LIBRARY_CONTENT: - // libs..classpath - return ID.substring(5, ID.length()-10); - - case PROJECT: - // project. - return ID.substring(8); - - case FILE: - // file.reference. - return ID.substring(15); - - case PLATFORM: - return ID; - - case VARIABLE: - return ID.substring(4, ID.indexOf('}')); // NOI18N - - case VARIABLE_CONTENT: - return ID.substring(6, ID.indexOf('}')) + ID.substring(ID.indexOf('}')+1); // NOI18N - - default: - assert false; - return ID; - } + ProblemReference( + @NonNull final ProjectProblem problem, + @NonNull final Project project, + final boolean global) { + assert problem != null; + this.problem = problem; + this.project = project; + this.global = global; } - public Library define() throws Exception { - return definer.call(); - } - public @Override String toString() { - return type + ":" + ID + (broken ? "" : "[fixed]"); - } + String getDisplayName() { + final String displayName = problem.getDisplayName(); + String message; + if (global) { + final String projectName = ProjectUtils.getInformation(project).getDisplayName(); + message = NbBundle.getMessage( + BrokenReferencesModel.class, + "FMT_ProblemInProject", + projectName, + displayName); - public @Override boolean equals(Object o) { - if (o == this) { - return true; + } else { + message = displayName; } - if (!(o instanceof OneReference)) { - return false; - } - OneReference or = (OneReference)o; - return (this.type == or.type && this.ID.equals(or.ID) && this.bprj.equals(or.bprj)); - } - - public @Override int hashCode() { - int result = 7 * type.hashCode(); - result = 31*result + ID.hashCode(); - return result; - } - - } - - public static final class BrokenProject { - private final Reference helper; - private final Reference referenceHelper; - private final Reference evaluator; - private final String[] properties; - private final String[] platformProperties; - - public BrokenProject( - @NonNull final AntProjectHelper helper, - @NonNull final ReferenceHelper referenceHelper, - @NonNull final PropertyEvaluator evaluator, - @NonNull final String[] properties, - @NonNull final String[] platformProperties) { - assert helper != null; - assert referenceHelper != null; - assert properties != null; - assert platformProperties != null; - this.helper = new WeakReference(helper); - this.referenceHelper = new WeakReference(referenceHelper); - this.evaluator = new WeakReference(evaluator); - this.properties = Arrays.copyOf(properties, properties.length); - this.platformProperties = Arrays.copyOf(platformProperties, platformProperties.length); - } - - AntProjectHelper getAntProjectHelper() { - return helper.get(); - } - - Project getProject() { - final AntProjectHelper h = getAntProjectHelper(); - return h == null ? null : FileOwnerQuery.getOwner(h.getProjectDirectory()); - } - - ReferenceHelper getReferenceHelper() { - return referenceHelper.get(); - } - - PropertyEvaluator getEvaluator() { - return evaluator.get(); - } - - String[] getProperties() { - return this.properties; - } - - String[] getPlatformProperties() { - return this.platformProperties; + return message; } @Override - public boolean equals(final Object other) { - if (!(other instanceof BrokenProject)) { - return false; - } - final AntProjectHelper myAPH = getAntProjectHelper(); - final AntProjectHelper otherAPH = ((BrokenProject)other).getAntProjectHelper(); - final FileObject myDir = myAPH == null ? null : myAPH.getProjectDirectory(); - final FileObject otherDir = otherAPH == null ? null : otherAPH.getProjectDirectory(); - return myDir == null ? otherDir == null : myDir.equals(otherDir); + @NonNull + public String toString() { + return String.format( + "Problem: %s %s", //NOI18N + problem, + resolved ? "resolved" : "unresolved"); //NOI18N } @Override public int hashCode() { - final AntProjectHelper h = getAntProjectHelper(); - return h == null ? 0 : h.getProjectDirectory().hashCode(); + int hash = 17; + hash = 31 * hash + problem.hashCode(); + hash = 31 * hash + project.hashCode(); + return hash; } + + @Override + public boolean equals (Object other) { + if (!(other instanceof ProblemReference)) { + return false; + } + final ProblemReference otherRef = (ProblemReference) other; + return problem.equals(otherRef.problem) && + project.equals(otherRef.project); + } + } - public static final class Context { - private final List toResolve; + static final class Context { + private final List toResolve; private final ChangeSupport support; public Context() { - toResolve = Collections.synchronizedList(new LinkedList()); + toResolve = Collections.synchronizedList(new LinkedList()); support = new ChangeSupport(this); - } + } - private Context(final @NonNull BrokenProject broken) { - this(); - this.offer(broken); - } - - public void offer(final BrokenProject broken) { + public void offer(@NonNull final Project broken) { assert broken != null; - this.toResolve.add(broken); - support.fireChange(); + if (broken.getLookup().lookup(ProjectProblemsProvider.class) != null) { + this.toResolve.add(broken); + support.fireChange(); + } } public boolean isEmpty() { return this.toResolve.isEmpty(); } - public BrokenProject[] getBrokenProjects() { + public Project[] getBrokenProjects() { synchronized (toResolve) { - return toResolve.toArray(new BrokenProject[toResolve.size()]); + return toResolve.toArray(new Project[toResolve.size()]); } } --- a/projectui/src/org/netbeans/modules/project/ui/problems/BrokenReferencesSettings.java +++ a/projectui/src/org/netbeans/modules/project/ui/problems/BrokenReferencesSettings.java @@ -0,0 +1,68 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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]" + * + * 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. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.project.ui.problems; + +import java.util.prefs.Preferences; +import org.openide.util.NbPreferences; + +/** + * + * @author Tomas Zezula + */ +public class BrokenReferencesSettings { + + private static final String PROP_SHOW_AGAIN_BROKEN_REF_ALERT = "showAgainBrokenRefAlert"; //NOI18N + + public static boolean isShowAgainBrokenRefAlert() { + return prefs().getBoolean(PROP_SHOW_AGAIN_BROKEN_REF_ALERT, true); + } + + public static void setShowAgainBrokenRefAlert(boolean again) { + prefs().putBoolean(PROP_SHOW_AGAIN_BROKEN_REF_ALERT, again); + } + + + private static Preferences prefs() { + return NbPreferences.forModule(BrokenReferencesSettings.class); + } + +} --- a/projectui/src/org/netbeans/modules/project/ui/problems/Bundle.properties +++ a/projectui/src/org/netbeans/modules/project/ui/problems/Bundle.properties @@ -0,0 +1,21 @@ +MSG_Broken_References_Label=Project Problems +MSG_BrokenReferencesAlertPanel_notAgain=&Do not show this message again +ACSN_BrokenReferencesAlertPanel_notAgain=Do not show this message again +ACSD_BrokenReferencesAlertPanel_notAgain=N/A +MSG_Broken_References=One or more project resources could not be found.
Right-click the project in the Projects window and choose
Resolve Project Problems to find the missing resources. +ACSN_BrokenReferencesAlertPanel=Broken Project Panel +ACSD_BrokenReferencesAlertPanel=N/A +FMT_ProblemInProject={1} (in {0}) + +LBL_BrokenLinksCustomizer_List=Project &Problems: +ACSD_BrokenLinksCustomizer_List=N/A +LBL_BrokenLinksCustomizer_Fix=&Resolve... +ACSD_BrokenLinksCustomizer_Fix=N/A +LBL_BrokenLinksCustomizer_Description=&Description\: +ACSD_BrokenLinksCustomizer_Description=N/A +ACSN_BrokenReferencesCustomizer=Broken Project Customizer +ACSD_BrokenReferencesCustomizer=N/A + +LBL_Fix_Broken_Links_Action=Resolve Project Problems... +MSG_OneProblem= Unresolved project problem, fix by "Resolve Project Problems". +MSG_MoreProblems= {0} unresolved project problems, fix by "Resolve Project Problems". --- a/projectuiapi/apichanges.xml +++ a/projectuiapi/apichanges.xml @@ -107,6 +107,25 @@ + + + Added an SPI to provide project metadata problems. + + + + + +

+ Added a SPI allowing project to provide project metadata problems. + These problems are shown in the Resolve Project Problems Customizer, + project with project problems are annotated by broken project badge. +

+
+ + + + +
Extracted findPath method into a new interface PathFinder --- a/projectuiapi/nbproject/project.properties +++ a/projectuiapi/nbproject/project.properties @@ -42,7 +42,7 @@ javac.compilerargs=-Xlint -Xlint:-serial javac.source=1.6 -spec.version.base=1.59.0 +spec.version.base=1.60.0 is.autoload=true javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml --- a/projectuiapi/src/org/netbeans/api/project/ui/ProjectProblems.java +++ a/projectuiapi/src/org/netbeans/api/project/ui/ProjectProblems.java @@ -0,0 +1,112 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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]" + * + * 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. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.api.project.ui; + +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.project.Project; +import org.netbeans.modules.project.uiapi.BrokenReferencesImplementation; +import org.netbeans.spi.project.ui.ProjectProblemsProvider; +import org.openide.util.Lookup; +import org.openide.util.Parameters; + +/** + * Support for managing project problems. Project freshly checkout from + * VCS can has broken references of several types: reference to other project, + * reference to a foreign file, reference to an external source root, reference + * to a library, etc. This class has helper methods for detection of these problems + * and for fixing them. + * + * @see ProjectProblemsProvider + * + * @since 1.60 + * @author Tomas Zezula + */ +public class ProjectProblems { + + private ProjectProblems() { + throw new IllegalStateException(); + } + + + /** + * Checks whether the project has some project problem. + * @param project the project to test for existence of project problems like broken references, missing server, etc. + * @return true if some problem was found and it is necessary to give + * user a chance to fix them. + */ + public static boolean isBroken(@NonNull final Project project) { + Parameters.notNull("project", project); //NOI18N + final ProjectProblemsProvider provider = project.getLookup().lookup(ProjectProblemsProvider.class); + return provider !=null && !provider.getProblems().isEmpty(); + } + + + /** + * Show alert message box informing user that a project has broken + * references. This method can be safely called from any thread, e.g. during + * the project opening, and it will take care about showing message box only + * once for several subsequent calls during a timeout. + * The alert box has also "show this warning again" check box and provides resolve + * broken references option + * @param project to show the alert for. + */ + public static void showAlert(@NonNull final Project project) { + Parameters.notNull("project", project); //NOI18N + final BrokenReferencesImplementation impl = Lookup.getDefault().lookup(BrokenReferencesImplementation.class); + if (impl != null) { + impl.showAlert(project); + } + } + + /** + * Shows an UI customizer which gives users chance to fix encountered problems. + * @param project the project for which the customizer should be shown. + */ + public static void showCustomizer(@NonNull final Project project) { + Parameters.notNull("project", project); //NOI18N + final BrokenReferencesImplementation impl = Lookup.getDefault().lookup(BrokenReferencesImplementation.class); + if (impl != null) { + impl.showCustomizer(project); + } + } + +} --- a/projectuiapi/src/org/netbeans/modules/project/uiapi/BrokenReferencesImplementation.java +++ a/projectuiapi/src/org/netbeans/modules/project/uiapi/BrokenReferencesImplementation.java @@ -0,0 +1,57 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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]" + * + * 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. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.project.uiapi; + +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.project.Project; + +/** + * + * @author Tomas Zezula + */ +public interface BrokenReferencesImplementation { + + void showAlert(@NonNull Project project); + + void showCustomizer(@NonNull Project project); + +} --- a/projectuiapi/src/org/netbeans/spi/project/ui/ProjectProblemResolver.java +++ a/projectuiapi/src/org/netbeans/spi/project/ui/ProjectProblemResolver.java @@ -0,0 +1,82 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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]" + * + * 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. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.spi.project.ui; + +import java.util.concurrent.Future; + +/** + * A fix of the {@link ProjectProblemsProvider.ProjectProblem}. + * As the {@link ProjectProblemResolver} is the only project specific + * part of the {@link ProjectProblemsProvider.ProjectProblem} it's used + * by {@link ProjectProblemsProvider.ProjectProblem#hashCode} and + * {@link ProjectProblemsProvider.ProjectProblem#equals} and therefore + * it should provide reasonable {@link Object#hashCode} and {@link Object#equals} + * implementation, for example based on project property which should be fixed. + * + * @since 1.60 + * @author Tomas Zezula + */ +public interface ProjectProblemResolver { + + /** + * Resolves the project problem. + * Resolves the {@link ProjectProblemsProvider.ProjectProblem} + * returning the {@link Future} holding the resolution result. + * The method is called by the Event Dispatch Thread. When the + * resolution needs to be done by a background thread, eg. downloading + * an archive from repository, the implementation directly returns + * a {@link Future} which is completed by the background thread. + * @return the {@link Future} holding the resolution result. + */ + Future resolve(); + + /** + * {@inheritDoc} + */ + boolean equals(Object other); + + /** + * {@inheritDoc} + */ + int hashCode(); + +} --- a/projectuiapi/src/org/netbeans/spi/project/ui/ProjectProblemsProvider.java +++ a/projectuiapi/src/org/netbeans/spi/project/ui/ProjectProblemsProvider.java @@ -0,0 +1,338 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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]" + * + * 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. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.spi.project.ui; + +import java.beans.PropertyChangeListener; +import java.util.Collection; +import java.util.concurrent.Future; +import org.netbeans.api.annotations.common.CheckForNull; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.annotations.common.NullAllowed; +import org.netbeans.spi.project.ui.support.UILookupMergerSupport; +import org.openide.util.Parameters; + +/** + * Provider of project metadata problems. + * The provider of various project metadata problems like broken reference to source root, + * broken reference to a library, wrong compiler options, etc. + * The provider implementation(s) are registered into the project lookup as well as + * the {@link org.netbeans.spi.project.LookupMerger} for them + * {@link UILookupMergerSupport#createProjectProblemsProviderMerger}. + * + *
+ *

The presence of the {@link ProjectProblemsProvider} in the project lookup + * automatically enable the broken project metadata badge on the project. + * If the project wants to provide the "Resolve Broken Project" action it needs + * to add a reference to the "org.netbeans.modules.project.ui.problems.BrokenProjectActionFactory" + * action with required position, for example using the ActionRefrecne annotation: + *

+ * @ActionReferences({
+    @ActionReference(
+        id=@ActionID(id="org.netbeans.modules.project.ui.problems.BrokenProjectActionFactory",category="Project"),
+        position = 2600,
+        path = "Projects/org-netbeans-modules-myproject/Actions")
+})
+ * 
+ *

+ *
+ * + * @author Tomas Zezula + * @since 1.60 + */ +public interface ProjectProblemsProvider { + + /** + * Name of the problems property. + */ + String PROP_PROBLEMS = "problems"; //NOI18N + + + /** + * Adds a {@link PropertyChangeListener} listening on change of project + * metadata problems. + * @param listener the listener to be added. + */ + void addPropertyChangeListener(@NonNull PropertyChangeListener listener); + + /** + * Removes a {@link PropertyChangeListener} listening on change of project + * metadata problems. + * @param listener the listener to be removed. + */ + void removePropertyChangeListener (@NonNull PropertyChangeListener listener); + + /** + * Returns project metadata problems found by this {@link ProjectProblemsProvider}. + * @return the problems + */ + @NonNull + Collection getProblems(); + + + /** + * The {@link ProjectProblem} resolution status. + */ + enum Status { + RESOLVED, + RESOLVED_WITH_WARNING, + UNRESOLVED + } + + + /** + * The {@link ProjectProblem} severity. + */ + enum Severity { + ERROR, + WARNING + } + + /** + * Result of the project metadata problem resolution. + */ + public final class Result { + private final Status status; + private final String message; + + private Result( + @NonNull final Status status, + @NullAllowed final String message) { + this.status = status; + this.message = message; + } + + /** + * Returns true if the problem was resolved. + * @return true if the problem was successfully resolved. + */ + public boolean isResolved() { + return status != Status.UNRESOLVED; + } + + /** + * Returns status of the resolution. + * @return the {@link ProjectProblemsProvider.Status} + */ + @NonNull + public Status getStatus() { + return status; + } + + /** + * Returns possible error or warning message. + * @return the message which should be presented to the user. + */ + @CheckForNull + public String getMessage() { + return message; + } + + /** + * Creates a new instance of the {@link Result}. + * @param status the status of the project problem resolution. + * @return the new {@link Result} instance. + */ + public static Result create( + @NonNull final Status status) { + Parameters.notNull("status", status); //NOI18N + return new Result(status, null); + } + + /** + * Creates a new instance of the {@link Result}. + * @param status the status of the project problem resolution. + * @param message the message which should be presented to the user. + * @return the new {@link Result} instance. + */ + public static Result create( + @NonNull final Status status, + @NonNull final String message) { + Parameters.notNull("status", status); //NOI18N + Parameters.notNull("message", message); //NOI18N + return new Result(status, message); + } + + } + + + /** + * Project metadata problem. + * Represents a problem in the project metadata which should be presented + * to the user and resolved. + */ + public final class ProjectProblem { + + private final Severity severity; + private final String displayName; + private final String description; + private final ProjectProblemResolver resolver; + + private ProjectProblem( + @NonNull final Severity severity, + @NonNull final String displayName, + @NonNull final String description, + @NonNull final ProjectProblemResolver resolver) { + Parameters.notNull("severity", severity); //NOI18N + Parameters.notNull("displayName", displayName); //NOI18N + Parameters.notNull("description", description); //NOI18N + Parameters.notNull("resolver", resolver); //NOI18N + this.severity = severity; + this.displayName = displayName; + this.description = description; + this.resolver = resolver; + } + + /** + * Returns a {@link ProjectProblem} severity. + * @return the {@link Severity} + */ + @NonNull + public Severity getSeverity() { + return severity; + } + + /** + * Returns a display name of the problem. + * The display name is presented to the user in the UI. + * @return the display name. + */ + @NonNull + public String getDisplayName() { + return displayName; + } + + /** + * Returns the description of the problem. + * The description is shown in the project problems details. + * @return project problem description. + */ + @NonNull + public String getDescription() { + return description; + } + + /** + * Resolves the problem. + * Called by the Event Dispatch Thread. + * When the resolution needs to be done by a background thread, eg. downloading + * an archive from repository, the implementation directly returns + * a {@link Future} which is completed by the background thread. + * @return the {@link Future} holding the problem resolution status. + */ + public Future resolve() { + return resolver.resolve(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(final Object other) { + if (other == this) { + return true; + } + if (!(other instanceof ProjectProblem)) { + return false; + } + final ProjectProblem otherProblem = (ProjectProblem) other; + return displayName.equals(otherProblem.displayName) && + description.equals(otherProblem.description) && + resolver.equals(otherProblem.resolver); + } + + /** + * {@inheritdoc} + */ + @Override + public int hashCode() { + int result = 17; + result = 31 * result + displayName.hashCode(); + result = 31 * result + description.hashCode(); + result = 31 * result + resolver.hashCode(); + return result; + } + + /** + * {@inheritdoc} + */ + @Override + public String toString() { + return String.format( + "Project Problem: %s, resolvable by: %s", //NOI18N + displayName, + resolver); + } + + + /** + * Creates a new instance of the {@link ProjectProblem} with error {@link Severity}. + * @param displayName the project problem display name. + * @param description the project problem description. + * @param resolver the {@link ProjectProblemResolver} to resolve the problem. + * @return a new instance of {@link ProjectProblem} + */ + @NonNull + public static ProjectProblem createError( + @NonNull final String displayName, + @NonNull final String description, + @NonNull final ProjectProblemResolver resolver) { + return new ProjectProblem(Severity.ERROR, displayName,description,resolver); + } + + /** + * Creates a new instance of the {@link ProjectProblem} with warning {@link Severity}. + * @param displayName the project problem display name. + * @param description the project problem description. + * @param resolver the {@link ProjectProblemResolver} to resolve the problem. + * @return a new instance of {@link ProjectProblem} + */ + @NonNull + public static ProjectProblem createWarning( + @NonNull final String displayName, + @NonNull final String description, + @NonNull final ProjectProblemResolver resolver) { + return new ProjectProblem(Severity.WARNING, displayName,description,resolver); + } + + } + +} --- a/projectuiapi/src/org/netbeans/spi/project/ui/support/UILookupMergerSupport.java +++ a/projectuiapi/src/org/netbeans/spi/project/ui/support/UILookupMergerSupport.java @@ -45,15 +45,26 @@ package org.netbeans.spi.project.ui.support; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; +import org.netbeans.api.annotations.common.NonNull; import org.netbeans.modules.project.uiapi.ProjectOpenedTrampoline; import org.netbeans.spi.project.LookupMerger; import org.netbeans.spi.project.ui.PrivilegedTemplates; import org.netbeans.spi.project.ui.ProjectOpenedHook; +import org.netbeans.spi.project.ui.ProjectProblemsProvider; import org.netbeans.spi.project.ui.RecommendedTemplates; import org.openide.util.Lookup; +import org.openide.util.Parameters; +import org.openide.util.WeakListeners; /** * Factory class for creation of {@link org.netbeans.spi.project.LookupMerger} instances. @@ -98,6 +109,20 @@ public static LookupMerger createProjectOpenHookMerger(ProjectOpenedHook defaultInstance) { return new OpenMerger(defaultInstance); } + + + /** + * Create a {@link org.netbeans.spi.project.LookupMerger} instance + * for {@link org.netbeans.spi.project.ui.ProjectProblemsProvider}. The merger + * collects all {@link org.netbeans.spi.project.ui.ProjectProblemsProvider.ProjectProblem}s + * from {@link org.netbeans.spi.project.ui.ProjectProblemsProvider}s registered in the + * project lookup. + * @return instance to include in project lookup + * @since 1.60 + */ + public static LookupMerger createProjectProblemsProviderMerger() { + return new ProjectProblemsProviderMerger(); + } private static class PrivilegedMerger implements LookupMerger { public Class getMergeableClass() { @@ -135,6 +160,20 @@ } } + + private static class ProjectProblemsProviderMerger implements LookupMerger { + + @Override + public Class getMergeableClass() { + return ProjectProblemsProvider.class; + } + + @Override + public ProjectProblemsProvider merge(Lookup lookup) { + return new ProjectProblemsProviderImpl(lookup); + } + + } private static class PrivilegedTemplatesImpl implements PrivilegedTemplates { @@ -215,5 +254,61 @@ } - + private static class ProjectProblemsProviderImpl implements ProjectProblemsProvider, PropertyChangeListener { + + private final Lookup lkp; + private final PropertyChangeSupport support; + //@GuardedBy("this") + private Iterable> providers; + + + ProjectProblemsProviderImpl(@NonNull final Lookup lkp) { + Parameters.notNull("lkp", lkp); //NOI18N + this.lkp = lkp; + this.support = new PropertyChangeSupport(this); + } + + @Override + public void addPropertyChangeListener(PropertyChangeListener listener) { + Parameters.notNull("listener", listener); //NOI18N + support.addPropertyChangeListener(listener); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener listener) { + Parameters.notNull("listener", listener); //NOI18N + support.removePropertyChangeListener(listener); + } + + @Override + public Collection getProblems() { + final Collection problems = new LinkedHashSet(); + for (Reference providerRef : getProviders()) { + final ProjectProblemsProvider provider = providerRef.get(); + if (provider != null) { + problems.addAll(provider.getProblems()); + } + } + return Collections.unmodifiableCollection(problems); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (PROP_PROBLEMS.equals(evt.getPropertyName())) { + support.firePropertyChange(PROP_PROBLEMS,null,null); + } + } + + private synchronized Iterable> getProviders() { + if (providers == null) { + final Collection> newProviders = new LinkedHashSet>(); + for (ProjectProblemsProvider provider : lkp.lookupAll(ProjectProblemsProvider.class)) { + provider.addPropertyChangeListener(WeakListeners.propertyChange(this, provider)); + newProviders.add(new WeakReference(provider)); + } + providers = newProviders; + } + return providers; + } + } }