diff -r ffef4cca2ff4 project.libraries/apichanges.xml --- a/project.libraries/apichanges.xml Tue Jan 22 05:32:07 2008 +0000 +++ b/project.libraries/apichanges.xml Thu Feb 21 14:38:17 2008 +0100 @@ -40,8 +40,8 @@ Version 2 license, then the option applies only if the new code is made subject to such option by the copyright holder. --> - - + + + + + + Support for project libraries + + + + +

+ LibraryManager.addLibrary will not work with areas, + so it was deprecated in favor of the new createLibrary. +

+

+ Code which obtained a Library of arbitrary origin, + and then assumed it could refer to that library henceforth by name only, + will not work with project libraries. + The simplest persistent identifier for a new-style library is pair of name + and URL of manager (null for the default manager). +

+
+ +

+ There is a new API and SPI for project libraries + (or, more generally, libraries with specific storage locations). + ArealLibraryProvider, + and LibraryStorageArea are new, as are methods + Library.getManager, + Library.getRawContent(String) + LibraryManager.getDisplayName, + LibraryManager.getLocation, + LibraryManager.createLibrary, + LibraryManager.forLocation, and + LibraryManager.getOpenManagers. + (LibraryProvider was also generified.) + LibrariesSupport has few additional helper methods. +

+

+ There's also new methods and classes for UI customizations of Libraries, eg. + LibrariesCustomizer.showCreateNewLibraryCustomizer(LibraryManager), + LibrariesCustomizer.showCustomizer(Library, LibraryManager), + LibrariesCustomizer.showSingleLibraryCustomizer(Library), + and LibraryChooser class. +

+
+ + + + + + + + + +
diff -r ffef4cca2ff4 project.libraries/src/org/netbeans/api/project/libraries/LibrariesCustomizer.java --- a/project.libraries/src/org/netbeans/api/project/libraries/LibrariesCustomizer.java Tue Jan 22 05:32:07 2008 +0000 +++ b/project.libraries/src/org/netbeans/api/project/libraries/LibrariesCustomizer.java Thu Feb 21 14:38:17 2008 +0100 @@ -45,6 +45,13 @@ import org.openide.DialogDisplayer; import org.openide.util.NbBundle; import java.awt.Dialog; +import javax.swing.border.EmptyBorder; +import org.netbeans.modules.project.libraries.LibraryTypeRegistry; +import org.netbeans.modules.project.libraries.ui.LibrariesModel; +import org.netbeans.modules.project.libraries.ui.NewLibraryPanel; +import org.netbeans.spi.project.libraries.LibraryImplementation; +import org.netbeans.spi.project.libraries.LibraryStorageArea; +import org.netbeans.spi.project.libraries.LibraryTypeProvider; /** Provides method for opening Libraries customizer * @@ -55,32 +62,116 @@ } /** - * Shows libraries customizer + * Shows libraries customizer for given library manager. + * @param activeLibrary if not null the activeLibrary is selected in the opened customizer + * @return true if user pressed OK and libraries were sucessfully modified + */ + public static boolean showCustomizer (Library activeLibrary, LibraryManager libraryManager) { + org.netbeans.modules.project.libraries.ui.LibrariesCustomizer customizer = + new org.netbeans.modules.project.libraries.ui.LibrariesCustomizer (libraryManager.getArea()); + customizer.setBorder(new EmptyBorder(12, 12, 0, 12)); + if (activeLibrary != null) + customizer.setSelectedLibrary (activeLibrary.getLibraryImplementation ()); + DialogDescriptor descriptor = new DialogDescriptor (customizer,NbBundle.getMessage(LibrariesCustomizer.class, + "TXT_LibrariesManager")); + Dialog dlg = DialogDisplayer.getDefault().createDialog(descriptor); + try { + dlg.setVisible(true); + if (descriptor.getValue() == DialogDescriptor.OK_OPTION) { + return customizer.apply(); + } else { + return false; + } + } finally { + dlg.dispose(); + } + } + + /** + * Shows libraries customizer for global libraries. * @param activeLibrary if not null the activeLibrary is selected in the opened customizer * @return true if user pressed OK and libraries were sucessfully modified */ public static boolean showCustomizer (Library activeLibrary) { + return showCustomizer(activeLibrary, LibraryManager.getDefault()); + } + + /** + * Show customizer for creating new library in the given library manager. + * @param manager manager + * @return created persisted library or null if user cancelled operation + * @since org.netbeans.modules.project.libraries/1 1.16 + */ + public static Library showCreateNewLibraryCustomizer(LibraryManager manager) { + if (manager == null) { + manager = LibraryManager.getDefault(); + } + LibraryStorageArea area = manager.getArea(); + if (area == null) { + area = LibrariesModel.GLOBAL_AREA; + } org.netbeans.modules.project.libraries.ui.LibrariesCustomizer customizer = - new org.netbeans.modules.project.libraries.ui.LibrariesCustomizer (); - if (activeLibrary != null) - customizer.setSelectedLibrary (activeLibrary.getLibraryImplementation ()); + new org.netbeans.modules.project.libraries.ui.LibrariesCustomizer (area); + NewLibraryPanel p = new NewLibraryPanel(customizer.getModel(), null, area); + DialogDescriptor dd = new DialogDescriptor (p, + NbBundle.getMessage(LibrariesCustomizer.class,"LibrariesCustomizer.createLibrary.title"), + true, DialogDescriptor.OK_CANCEL_OPTION, null, null); + p.setDialogDescriptor(dd); + Dialog dlg = DialogDisplayer.getDefault().createDialog (dd); + dlg.setVisible(true); + if (dd.getValue() == DialogDescriptor.OK_OPTION) { + LibraryImplementation impl; + if (area != LibrariesModel.GLOBAL_AREA) { + impl = customizer.getModel().createArealLibrary(p.getLibraryType(), p.getLibraryName(), manager.getArea()); + } else { + LibraryTypeProvider provider = LibraryTypeRegistry.getDefault().getLibraryTypeProvider(p.getLibraryType()); + if (provider == null) { + return null; + } + impl = provider.createLibrary(); + impl.setName(p.getLibraryName()); + } + customizer.getModel().addLibrary(impl); + customizer.forceTreeRecreation(); + if (customizeLibrary(customizer, impl)) { + return manager.getLibrary(impl.getName()); + } + } + return null; + } + + /** + * Show library customizer for the given library. + * @param library library + * @return true if library was modified or not + * @since org.netbeans.modules.project.libraries/1 1.16 + */ + public static boolean showSingleLibraryCustomizer(Library library) { + org.netbeans.modules.project.libraries.ui.LibrariesCustomizer customizer = + new org.netbeans.modules.project.libraries.ui.LibrariesCustomizer (library.getManager().getArea()); + return customizeLibrary(customizer, library.getLibraryImplementation()); + } + + private static boolean customizeLibrary(org.netbeans.modules.project.libraries.ui.LibrariesCustomizer customizer, + LibraryImplementation activeLibrary) { + customizer.hideLibrariesList(); + customizer.setBorder(new EmptyBorder(12, 8, 0, 10)); + customizer.setSelectedLibrary (activeLibrary); DialogDescriptor descriptor = new DialogDescriptor (customizer,NbBundle.getMessage(LibrariesCustomizer.class, - "TXT_LibrariesManager")); - Dialog dlg = null; + "LibrariesCustomizer.customizeLibrary.title")); + Dialog dlg = DialogDisplayer.getDefault().createDialog(descriptor); try { - dlg = DialogDisplayer.getDefault().createDialog (descriptor); dlg.setVisible(true); if (descriptor.getValue() == DialogDescriptor.OK_OPTION) { - return customizer.apply(); - } - else { - customizer.cancel(); + customizer.apply(); + return true; + } else { + return false; } } finally { - if (dlg != null) - dlg.dispose(); + dlg.dispose(); } - return false; } + } diff -r ffef4cca2ff4 project.libraries/src/org/netbeans/api/project/libraries/Library.java --- a/project.libraries/src/org/netbeans/api/project/libraries/Library.java Tue Jan 22 05:32:07 2008 +0000 +++ b/project.libraries/src/org/netbeans/api/project/libraries/Library.java Thu Feb 21 14:38:17 2008 +0100 @@ -45,12 +45,12 @@ import java.beans.PropertyChangeEvent; import java.net.URL; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.MissingResourceException; import java.util.ResourceBundle; import org.netbeans.modules.project.libraries.LibraryAccessor; import org.netbeans.spi.project.libraries.LibraryImplementation; +import org.netbeans.spi.project.libraries.support.LibrariesSupport; import org.openide.ErrorManager; import org.openide.util.NbBundle; @@ -76,11 +76,9 @@ private List listeners; - /** - * Creates new library instance - * - */ - private Library (LibraryImplementation impl) { + private final LibraryManager manager; + + Library(LibraryImplementation impl, LibraryManager manager) { this.impl = impl; this.impl.addPropertyChangeListener (new PropertyChangeListener () { public void propertyChange(PropertyChangeEvent evt) { @@ -88,7 +86,39 @@ Library.this.fireChange (propName,evt.getOldValue(),evt.getNewValue()); } }); + this.manager = manager; } // end create + + /** + * Gets the associated library manager. + * @return the manager (may be the "default" global manager, or a local manager) + * @since org.netbeans.modules.project.libraries/1 1.15 + */ + public LibraryManager getManager() { + return manager; + } + + /** + * Access typed library data. Any relative URL provided by SPI is made absolute + * before being passed to client. See {@link #getRawContent} if you need raw library data. + *

+ * The contents are defined by SPI providers and identified + * by the volume types. For example the j2se library supports the following + * volume types: classpath - the library classpath roots, src - the library sources, javadoc - the library javadoc. + * Your module must have contract with a particular provider's module to be able to query it effectively. + *

+ * + * @param volumeType which resources to return. + * @return path of URLs of given type (possibly empty but never null) + */ + public List getContent(final String volumeType) { + List urls = this.impl.getContent (volumeType); + List resolvedUrls = new ArrayList(urls.size()); + for (URL u : urls) { + resolvedUrls.add(LibrariesSupport.resolveLibraryEntryURL(manager.getLocation(), u)); + } + return resolvedUrls; + } // end getContent /** * Access typed but raw library data. @@ -102,10 +132,9 @@ * @param volumeType which resources to return. * @return path of URLs of given type (possibly empty but never null) */ - public List getContent(final String volumeType) { + public List getRawContent(final String volumeType) { return this.impl.getContent (volumeType); } // end getContent - /** @@ -169,11 +198,6 @@ return impl.hashCode(); } - @Override - public String toString () { - return this.getName(); - } - /** * Adds PropertyChangeListener * @param listener @@ -235,11 +259,16 @@ return key; } } + + @Override + public String toString() { + return "Library[" + getName() + "]"; // NOI18N + } static { LibraryAccessor.DEFAULT = new LibraryAccessor () { public Library createLibrary (LibraryImplementation impl) { - return new Library (impl); + return new Library(impl, LibraryManager.getDefault()); } }; } diff -r ffef4cca2ff4 project.libraries/src/org/netbeans/api/project/libraries/LibraryChooser.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/project.libraries/src/org/netbeans/api/project/libraries/LibraryChooser.java Thu Feb 21 14:38:17 2008 +0100 @@ -0,0 +1,140 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.api.project.libraries; + +import java.awt.Component; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.io.IOException; +import java.util.Set; +import javax.swing.JFileChooser; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle; + +/** + * Visual picker for libraries. + * @since org.netbeans.modules.project.libraries/1 1.16 + */ +public final class LibraryChooser { + + private LibraryChooser() { + } + + /** + * Create a chooser showing libraries from given library manager and let the + * user to pick some. + * @param manager manager; can be null in which case global libraries are listed + * @param filter optional libraries filter; null for no filtering + * @param handler handler to perform library importing; can be null in which case + * import will not be allowed in UI + * @return a nonempty set of libraries that were selected, or null if the dialog was cancelled + */ + public static Set showDialog(LibraryManager manager, Filter filter, LibraryImportHandler handler) { + return LibraryChooserGUI.showChooser(manager, filter, handler, true); + } + + /** + * Create a picker as an embeddable panel. + * Might be used in a wizard, for example. + * @param manager library manager to use or null for global libraries + * @param filter optional libraries filter; null for no filtering + * @return a panel controller + */ + public static Panel createPanel(LibraryManager manager, Filter filter) { + return LibraryChooserGUI.createPanel(manager, filter); + } + + /** + * Filter for use by {@link LibraryChooser#createPanel()} or + * {@link LibraryChooser#showDialog()}. + */ + public interface Filter { + + /** + * Accepts or rejects a library. + * @param library a library found in one of the managers + * @return true to display, false to hide + */ + boolean accept(Library library); + + } + + /** + * Represents operations permitted by {@link #createPanel}. + * Not to be implemented by foreign code (methods may be added in the future). + */ + public interface Panel { + + /** + * Produces the actual component you can display. + * @return an embeddable GUI component + */ + Component getVisualComponent(); + + /** + * Gets the set of libraries which are currently selected. + * @return a (possibly empty) set of libraries + */ + Set getSelectedLibraries(); + + /** + * Property fired when {@link #getSelectedLibraries} changes. + * Do not expect the old and new values to be non-null. + */ + String PROP_SELECTED_LIBRARIES = "selectedLibraries"; // NOI18N + + /** + * Add a listener for {@link #PROP_SELECTED_LIBRARIES}. + * @param listener the listener to add + */ + void addPropertyChangeListener(PropertyChangeListener listener); + + /** + * Remove a listener. + * @param listener the listener to remove + */ + void removePropertyChangeListener(PropertyChangeListener listener); + + } + + /** + * Handler for library importing. The handler is used from library chooser + * UI in order to import global library to sharable libraries location. A + * library is only imported if there is no library with the same library + * name in destination library manager. + */ + public interface LibraryImportHandler { + + /** + * Implementation is expected to copy given global library to + * sharable libraries location, that is to library manager the library + * chooser was created for. + * + * @param library library to copy + * @return newly created library + * @throws java.io.IOException any IO failure + * @throws IllegalArgumentException if there already exists library + * with this name + */ + Library importLibrary(Library library) throws IOException; + } + +} diff -r ffef4cca2ff4 project.libraries/src/org/netbeans/api/project/libraries/LibraryChooserGUI.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/project.libraries/src/org/netbeans/api/project/libraries/LibraryChooserGUI.java Thu Feb 21 14:38:17 2008 +0100 @@ -0,0 +1,382 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.api.project.libraries; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Image; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyVetoException; +import java.io.IOException; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.border.EmptyBorder; +import org.openide.DialogDescriptor; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.explorer.ExplorerManager; +import org.openide.filesystems.Repository; +import org.openide.loaders.DataFolder; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.nodes.NodeNotFoundException; +import org.openide.nodes.NodeOp; +import org.openide.util.Exceptions; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; + +class LibraryChooserGUI extends JPanel implements ExplorerManager.Provider, HelpCtx.Provider, LibraryChooser.Panel { + + private final LibraryManager manager; + private final LibraryChooser.Filter filter; + private final ExplorerManager explorer; + private LibraryChooser.LibraryImportHandler importHandler; + + private LibraryChooserGUI(LibraryManager manager, LibraryChooser.Filter filter, + LibraryChooser.LibraryImportHandler importHandler) { + if (manager == null) { + manager = LibraryManager.getDefault(); + } + this.manager = manager; + this.filter = filter; + this.importHandler = importHandler; + explorer = new ExplorerManager(); + initComponents(); + tree.setDefaultActionAllowed(false); + } + + public static LibraryChooser.Panel createPanel(LibraryManager manager, + LibraryChooser.Filter filter) { + LibraryChooserGUI l = new LibraryChooserGUI(manager, filter, null); + l.configureForEmbedded(); + return l; + } + + public static Set showChooser(LibraryManager manager, LibraryChooser.Filter filter, + LibraryChooser.LibraryImportHandler handler, boolean addOperation) { + LibraryChooserGUI l = new LibraryChooserGUI(manager, filter, handler); + return l.showDialog(addOperation); + } + + private Set showDialog(boolean addOperatation) { + // show manage button only in embedded panel: + manageLibrariesButton.setVisible(false); + // import enabled only for non-global library manager + importButton.setVisible(manager.getLocation() != null && importHandler != null); + JPanel inset = new JPanel(new BorderLayout()); + inset.setBorder(new EmptyBorder(12,12,0,12)); + inset.add(this); + String title; + String buttonLabel; + if (addOperatation) { + title = NbBundle.getMessage(LibraryChooserGUI.class, "LibraryChooserGUI.add.title"); + buttonLabel = NbBundle.getMessage(LibraryChooserGUI.class, "LibraryChooserGUI.add.button"); + } else { + title = NbBundle.getMessage(LibraryChooserGUI.class, "LibraryChooserGUI.import.title"); + buttonLabel = NbBundle.getMessage(LibraryChooserGUI.class, "LibraryChooserGUI.import.button"); + createButton.setVisible(false); + } + DialogDescriptor dd = new DialogDescriptor(inset, title); + dd.setModal(true); + final JButton add = new JButton(buttonLabel); + add.setEnabled(false); + add.setDefaultCapable(true); + explorer.addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + add.setEnabled(!getSelectedLibraries().isEmpty()); + } + }); + dd.setOptions(new Object[] {add, NotifyDescriptor.CANCEL_OPTION}); + dd.setClosingOptions(new Object[] {add, NotifyDescriptor.CANCEL_OPTION}); + if (DialogDisplayer.getDefault().notify(dd) == add) { + Set selection = getSelectedLibraries(); + assert !selection.isEmpty(); + return selection; + } else { + return null; + } + } + + private void configureForEmbedded() { + explorer.addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + firePropertyChange(PROP_SELECTED_LIBRARIES, null, null); + } + }); + createButton.setVisible(false); + importButton.setVisible(false); + } + + public Set getSelectedLibraries() { + Set s = new HashSet(); + for (Node n : explorer.getSelectedNodes()) { + Library l = n.getLookup().lookup(Library.class); + if (l != null) { + s.add(l); + } else { + return Collections.emptySet(); + } + } + return s; + } + + public Component getVisualComponent() { + return this; + } + + public ExplorerManager getExplorerManager() { + return explorer; + } + + public HelpCtx getHelpCtx() { + return new HelpCtx(LibraryChooserGUI.class); + } + + private void setRootNode() { + explorer.setRootContext(new AbstractNode(new LibraryManagerChildren())); + tree.expandAll(); + try { + if (explorer.getRootContext().getChildren().getNodes(true).length > 0) { + explorer.setSelectedNodes(new Node[] {explorer.getRootContext().getChildren().getNodes(true)[0]}); + } + } catch (PropertyVetoException x) { + Exceptions.printStackTrace(x); + } + /* XXX Nothing seems to work to scroll to top; how is it done? + tree.getViewport().setViewPosition(new Point()); + tree.getViewport().scrollRectToVisible(new Rectangle(0, 0, 1, 1)); + */ + tree.requestFocus(); + } + + @Override + public void addNotify() { + super.addNotify(); + setRootNode(); + } + + private class LibraryManagerChildren extends Children.Keys { + + @Override + protected void addNotify() { + super.addNotify(); + if (manager != null) { + setKeys(Collections.singleton(manager)); + } + } + + protected Node[] createNodes(LibraryManager mgr) { + List libs = new ArrayList(); + for (Library lib : mgr.getLibraries()) { + if (filter == null || filter.accept(lib)) { + libs.add(lib); + } + } + if (libs.isEmpty()) { + return new Node[0]; + } else { + Collections.sort(libs,new Comparator() { + Collator COLL = Collator.getInstance(); + public int compare(Library lib1, Library lib2) { + return COLL.compare(lib1.getDisplayName(), lib2.getDisplayName()); + } + }); + Node n = new AbstractNode(new LibraryChildren(libs)) { + Node iconDelegate = DataFolder.findFolder(Repository.getDefault().getDefaultFileSystem().getRoot()).getNodeDelegate(); + public Image getIcon(int type) { + return iconDelegate.getIcon(type); + } + public Image getOpenedIcon(int type) { + return iconDelegate.getOpenedIcon(type); + } + }; + n.setName(mgr.getDisplayName()); + n.setDisplayName(mgr.getDisplayName()); + return new Node[] {n}; + } + } + + } + + private class LibraryChildren extends Children.Keys { + + LibraryChildren(List libs) { + setKeys(libs); + } + + protected Node[] createNodes(Library lib) { + AbstractNode n = new AbstractNode(Children.LEAF, Lookups.singleton(lib)); + n.setName(lib.getName()); + n.setDisplayName(lib.getDisplayName()); + n.setShortDescription(lib.getDescription()); + n.setIconBaseWithExtension("org/netbeans/modules/project/libraries/resources/libraries.gif"); // NOI18N + return new Node[] {n}; + } + + } + + // //GEN-BEGIN:initComponents + private void initComponents() { + + librariesLabel = new javax.swing.JLabel(); + tree = new org.openide.explorer.view.BeanTreeView(); + createButton = new javax.swing.JButton(); + importButton = new javax.swing.JButton(); + manageLibrariesButton = new javax.swing.JButton(); + + librariesLabel.setLabelFor(tree); + org.openide.awt.Mnemonics.setLocalizedText(librariesLabel, org.openide.util.NbBundle.getMessage(LibraryChooserGUI.class, "LibraryChooserGUI.librariesLabel")); // NOI18N + + tree.setBorder(javax.swing.BorderFactory.createEtchedBorder()); + tree.setPopupAllowed(false); + tree.setRootVisible(false); + + createButton.setText(org.openide.util.NbBundle.getMessage(LibraryChooserGUI.class, "LibraryChooserGUI.createButton.text")); // NOI18N + createButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + createButtonActionPerformed(evt); + } + }); + + importButton.setText(org.openide.util.NbBundle.getMessage(LibraryChooserGUI.class, "LibraryChooserGUI.importButton.text")); // NOI18N + importButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + importButtonActionPerformed(evt); + } + }); + + manageLibrariesButton.setText(org.openide.util.NbBundle.getMessage(LibraryChooserGUI.class, "LibraryChooserGUI.manageLibrariesButton.text")); // NOI18N + manageLibrariesButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + manageLibrariesButtonActionPerformed(evt); + } + }); + + org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(librariesLabel) + .add(org.jdesktop.layout.GroupLayout.TRAILING, layout.createSequentialGroup() + .add(tree, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 257, Short.MAX_VALUE) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING) + .add(createButton) + .add(importButton))) + .add(layout.createSequentialGroup() + .add(manageLibrariesButton) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .add(librariesLabel) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING) + .add(layout.createSequentialGroup() + .add(createButton) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(importButton)) + .add(tree, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 284, Short.MAX_VALUE)) + .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED) + .add(manageLibrariesButton)) + ); + + getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(LibraryChooserGUI.class, "LibraryChooserGUI.accessibleDescription")); // NOI18N + }// //GEN-END:initComponents + + private void manageLibrariesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_manageLibrariesButtonActionPerformed + if (LibrariesCustomizer.showCustomizer(null, manager)) { + setRootNode(); + } + }//GEN-LAST:event_manageLibrariesButtonActionPerformed + + private void createButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_createButtonActionPerformed + Library l = LibrariesCustomizer.showCreateNewLibraryCustomizer(manager); + if (l != null) { + setRootNode(); + selectLibrary(Collections.singleton(l)); + } + }//GEN-LAST:event_createButtonActionPerformed + + private void selectLibrary(Collection libraries) { + Node root = explorer.getRootContext(); + List selection = new ArrayList(); + for (Library lib : libraries) { + String[] path = {lib.getManager().getDisplayName(), lib.getName()}; + try { + Node node = NodeOp.findPath(root, path); + if (node != null) { + selection.add(node); + } + } catch (NodeNotFoundException e) { + //Ignore it + } + } + try { + explorer.setSelectedNodes(selection.toArray(new Node[selection.size()])); + } catch (PropertyVetoException e) { + //Ignore it + } + } + + private void importButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_importButtonActionPerformed + Set libs = showChooser(LibraryManager.getDefault(), + new IgnoreAlreadyImportedLibrariesFilter(), null, false); + if (libs != null) { + Set importedLibs = new HashSet(); + try { + for (Library lib : libs) { + importedLibs.add(importHandler.importLibrary(lib)); + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + setRootNode(); + selectLibrary(importedLibs); + } + }//GEN-LAST:event_importButtonActionPerformed + + private class IgnoreAlreadyImportedLibrariesFilter implements LibraryChooser.Filter { + public boolean accept(Library library) { + return manager.getLibrary(library.getName()) == null; + } + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton createButton; + private javax.swing.JButton importButton; + private javax.swing.JLabel librariesLabel; + private javax.swing.JButton manageLibrariesButton; + private org.openide.explorer.view.BeanTreeView tree; + // End of variables declaration//GEN-END:variables + +} diff -r ffef4cca2ff4 project.libraries/src/org/netbeans/api/project/libraries/LibraryManager.java --- a/project.libraries/src/org/netbeans/api/project/libraries/LibraryManager.java Tue Jan 22 05:32:07 2008 +0000 +++ b/project.libraries/src/org/netbeans/api/project/libraries/LibraryManager.java Thu Feb 21 14:38:17 2008 +0100 @@ -41,24 +41,29 @@ package org.netbeans.api.project.libraries; - -import org.netbeans.api.project.libraries.Library; -import org.netbeans.spi.project.libraries.LibraryImplementation; -import org.netbeans.spi.project.libraries.LibraryProvider; -import org.openide.util.Lookup; -import org.openide.util.LookupListener; -import org.openide.util.LookupEvent; - import java.beans.PropertyChangeSupport; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeEvent; import java.io.IOException; -import java.util.*; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.netbeans.modules.project.libraries.LibraryAccessor; import org.netbeans.modules.project.libraries.WritableLibraryProvider; -import org.netbeans.spi.project.libraries.LibraryFactory; +import org.netbeans.modules.project.libraries.ui.LibrariesModel; +import org.netbeans.spi.project.libraries.ArealLibraryProvider; +import org.netbeans.spi.project.libraries.LibraryImplementation; +import org.netbeans.spi.project.libraries.LibraryProvider; +import org.netbeans.spi.project.libraries.LibraryStorageArea; +import org.netbeans.spi.project.libraries.LibraryTypeProvider; import org.netbeans.spi.project.libraries.support.LibrariesSupport; - -// XXX make getLibraries return Set not array +import org.openide.util.Lookup; +import org.openide.util.LookupListener; +import org.openide.util.LookupEvent; /** * LibraryManager provides registry of the installed libraries. @@ -67,19 +72,79 @@ */ public final class LibraryManager { + /** + * Property fired when the set of libraries changes. + */ public static final String PROP_LIBRARIES = "libraries"; //NOI18N private static LibraryManager instance; private Lookup.Result result; - private Collection currentStorages = new ArrayList(); - private PropertyChangeListener plistener; - private PropertyChangeSupport listeners; + private final Collection currentStorages = new ArrayList(); + private final PropertyChangeListener plistener = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + if (LibraryProvider.PROP_LIBRARIES.equals(evt.getPropertyName())) { + resetCache(); + } + } + }; + private final PropertyChangeSupport listeners = new PropertyChangeSupport(this); + private static final PropertyChangeSupport openLibraryManagerListListeners = + new PropertyChangeSupport(LibraryManager.class); + private static final PropertyChangeListener AREAL_LIBRARY_PROVIDER_LISTENER = new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + openLibraryManagerListListeners.firePropertyChange(PROP_OPEN_LIBRARY_MANAGERS, null, null); + } + }; + + /** Property fired when list of open library managers changes. */ + public static final String PROP_OPEN_LIBRARY_MANAGERS = "openManagers"; // NOI18N + private static Lookup.Result areaProvidersLookupResult = null; + private static Collection currentAreaProviders = new ArrayList(); + private Collection cache; - + /** null for default manager */ + private final ArealLibraryProvider alp; + /** null for default manager */ + private final LibraryStorageArea area; private LibraryManager () { - this.listeners = new PropertyChangeSupport(this); + alp = null; + area = null; + } + + private LibraryManager(ArealLibraryProvider alp, LibraryStorageArea area) { + this.alp = alp; + this.area = area; + LibraryProvider lp = LibraryAccessor.getLibraries(alp, area); + lp.addPropertyChangeListener(plistener); + currentStorages.add(lp); + } + + /** + * Gets a human-readable description of this manager. + * This may be used to visually differentiate the global manager from various local managers. + * @return a localized display name + * @see LibraryStorageArea#getDisplayName + * @since org.netbeans.modules.project.libraries/1 1.15 + */ + public String getDisplayName() { + if (area == null) { + return LibrariesModel.GLOBAL_AREA.getDisplayName(); + } else { + return area.getDisplayName(); + } + } + + /** + * Gets the location associated with this manager. + * @return a location where library definitions are kept, or null in the case of {@link #getDefault} + * @see LibraryStorageArea#getLocation + * @see #forLocation + * @since org.netbeans.modules.project.libraries/1 1.15 + */ + public URL getLocation() { + return area != null ? area.getLocation() : null; } /** @@ -99,44 +164,44 @@ } /** - * List all library defined in the IDE. - * - * @return Library[] library definitions never null + * Lists all libraries defined in this manager. + * @return library definitions (never null) */ public synchronized Library[] getLibraries() { if (this.cache == null) { - if (this.result == null) { - plistener = new PropertyChangeListener() { - public void propertyChange(PropertyChangeEvent evt) { - resetCache (); + List l = new ArrayList(); + if (area == null) { + if (result == null) { + result = Lookup.getDefault().lookupResult(LibraryProvider.class); + result.addLookupListener(new LookupListener() { + public void resultChanged(LookupEvent ev) { + resetCache(); + } + }); + } + Collection instances = result.allInstances(); + Collection added = new HashSet(instances); + added.removeAll(currentStorages); + Collection removed = new HashSet(currentStorages); + removed.removeAll(instances); + currentStorages.clear(); + for (LibraryProvider storage : instances) { + this.currentStorages.add(storage); + for (LibraryImplementation impl : storage.getLibraries()) { + l.add(new Library(impl, this)); } - }; - result = Lookup.getDefault().lookupResult(LibraryProvider.class); - result.addLookupListener (new LookupListener() { - public void resultChanged(LookupEvent ev) { - resetCache (); - } - }); - } - List l = new ArrayList(); - Collection instances = result.allInstances(); - Collection added = new HashSet(instances); - added.removeAll (currentStorages); - Collection removed = new HashSet(currentStorages); - removed.removeAll (instances); - currentStorages.clear(); - for (LibraryProvider storage : instances) { - this.currentStorages.add (storage); - for (LibraryImplementation impl : storage.getLibraries()) { - l.add(LibraryFactory.createLibrary(impl)); + } + for (LibraryProvider p : removed) { + p.removePropertyChangeListener(this.plistener); + } + for (LibraryProvider p : added) { + p.addPropertyChangeListener(this.plistener); + } + } else { + for (LibraryImplementation impl : currentStorages.iterator().next().getLibraries()) { + l.add(new Library(impl, this)); } } - for (LibraryProvider p : removed) { - p.removePropertyChangeListener(this.plistener); - } - for (LibraryProvider p : added) { - p.addPropertyChangeListener(this.plistener); - } this.cache = l; } return this.cache.toArray(new Library[this.cache.size()]); @@ -160,9 +225,11 @@ * @throws IOException when the library cannot be stored * @throws IllegalArgumentException if the library is not recognized by any * {@link org.netbeans.spi.project.libraries.LibraryTypeProvider} or the library - * of the same name already exists. + * of the same name already exists, or if this manager is not {@link #getDefault}. * @since org.netbeans.modules.project.libraries/1 1.14 + * @deprecated Use {@link #createLibrary} instead, as this properly supports local managers. */ + @Deprecated public void addLibrary (final Library library) throws IOException, IllegalArgumentException { assert library != null; if (LibrariesSupport.getLibraryTypeProvider(library.getType()) == null) { @@ -176,7 +243,41 @@ assert providers.size() == 1; providers.iterator().next().addLibrary(library.getLibraryImplementation()); } - + + /** + * Creates a new library definition and adds it to the list. + * @param type the type of library, as in {@link LibraryTypeProvider#getLibraryType} or {@link LibraryImplementation#getType} + * @param name the identifying name of the new library (must not duplicate a name already in use by a library in this manager) + * @param contents the initial contents of the library's volumes, as a map from volume type to volume content + * @return a newly created library + * @throws IOException if the new definition could not be stored + * @throws IllegalArgumentException if the library type or one of the content volume types is not supported, + * or if a library of the same name already exists in this manager + * @see ArealLibraryProvider#createLibrary + * @since org.netbeans.modules.project.libraries/1 1.15 + */ + public Library createLibrary(String type, String name, Map> contents) throws IOException { + if (getLibrary(name) != null) { + throw new IllegalArgumentException("Name already in use: " + name); // NOI18N + } + LibraryImplementation impl; + if (area == null) { + LibraryTypeProvider ltp = LibrariesSupport.getLibraryTypeProvider(type); + if (ltp == null) { + throw new IllegalArgumentException("Trying to add a library of unknown type: " + type); // NOI18N + } + impl = ltp.createLibrary(); + impl.setName(name); + for (Map.Entry> entry : contents.entrySet()) { + impl.setContent(entry.getKey(), entry.getValue()); + } + Lookup.getDefault().lookup(WritableLibraryProvider.class).addLibrary(impl); + } else { + impl = LibraryAccessor.createLibrary(alp, type, name, area, contents); + } + return new Library(impl, this); + } + /** * Removes installed library * @param library to be removed. @@ -187,9 +288,13 @@ */ public void removeLibrary (final Library library) throws IOException, IllegalArgumentException { assert library != null; - final Collection providers = Lookup.getDefault().lookupAll(WritableLibraryProvider.class); - assert providers.size() == 1; - providers.iterator().next().removeLibrary(library.getLibraryImplementation()); + if (area == null) { + final Collection providers = Lookup.getDefault().lookupAll(WritableLibraryProvider.class); + assert providers.size() == 1; + providers.iterator().next().removeLibrary(library.getLibraryImplementation()); + } else { + LibraryAccessor.remove(alp, library.getLibraryImplementation()); + } } /** @@ -229,7 +334,109 @@ return instance; } + /** + * Gets a library manager which loads library definitions from a particular location. + * There is no guarantee that the return value is the same object from call to call with the same location. + * @param location any storage location supported by an installed provider + * @return a library manager whose {@link #getLocation} matches the supplied location + * @throws IllegalArgumentException if no installed provider is able to manage locations of this kind + * @see ArealLibraryProvider#loadArea + * @see ArealLibraryProvider#getLibraries + * @since org.netbeans.modules.project.libraries/1 1.15 + */ + public static LibraryManager forLocation(URL location) throws IllegalArgumentException { + for (ArealLibraryProvider alp : Lookup.getDefault().lookupAll(ArealLibraryProvider.class)) { + LibraryStorageArea area = alp.loadArea(location); + if (area != null) { + return new LibraryManager(alp, area); + } + } + throw new IllegalArgumentException(location.toExternalForm()); + } + /** + * Gets an unspecified collection of managers which are somehow to be represented as open. + * For example, library storages referred to from open projects might be returned. + * You can listen on changes in list of open managers via {@link #addOpenManagersPropertyChangeListener}. + * There is no guarantee that the non-default managers are the same objects from call to call + * even if the locations remain the same. + * @see ArealLibraryProvider#getOpenAreas + * @return a set of managers, always including at least {@link #getDefault} + * @since org.netbeans.modules.project.libraries/1 1.15 + */ + public static Collection getOpenManagers() { + List managers = new ArrayList(); + managers.add(getDefault()); + Set locations = new HashSet(); + for (ArealLibraryProvider alp : Lookup.getDefault().lookupAll(ArealLibraryProvider.class)) { + for (LibraryStorageArea area : LibraryAccessor.getOpenAreas(alp)) { + if (locations.add(area.getLocation())) { + managers.add(new LibraryManager(alp, area)); + } + } + } + for (ArealLibraryProvider alp : Lookup.getDefault().lookupAll(ArealLibraryProvider.class)) { + for (URL location : LibrariesModel.createdAreas) { + LibraryStorageArea area = alp.loadArea(location); + if (area != null) { + assert area.getLocation().equals(location) : "Bad location " + area.getLocation() + " does not match " + location + " from " + alp.getClass().getName(); + if (locations.add(location)) { + managers.add(new LibraryManager(alp, area)); + } + } + } + } + return managers; + } + + /** + * Adds PropertyChangeListener on list of open library managers. + * The listener is notified when list of open library managers changes via + * {@link #PROP_OPEN_LIBRARY_MANAGERS}. + * @param listener to be notified + */ + public static synchronized void addOpenManagersPropertyChangeListener (PropertyChangeListener listener) { + assert listener != null; + if (areaProvidersLookupResult == null) { + areaProvidersLookupResult = Lookup.getDefault().lookupResult(ArealLibraryProvider.class); + attachListeners(areaProvidersLookupResult.allInstances()); + areaProvidersLookupResult.addLookupListener(new LookupListener() { + public void resultChanged(LookupEvent ev) { + attachListeners(areaProvidersLookupResult.allInstances()); + } + }); + } + openLibraryManagerListListeners.addPropertyChangeListener (listener); + } + + private static synchronized void attachListeners(Collection currentProviders) { + for (ArealLibraryProvider provider : currentAreaProviders) { + provider.removePropertyChangeListener(AREAL_LIBRARY_PROVIDER_LISTENER); + } + for (ArealLibraryProvider provider : currentProviders) { + provider.addPropertyChangeListener(AREAL_LIBRARY_PROVIDER_LISTENER); + } + currentAreaProviders = currentProviders; + } + + /** + * Removes PropertyChangeListener + * @param listener + */ + public static void removeOpenManagersPropertyChangeListener (PropertyChangeListener listener) { + assert listener != null; + openLibraryManagerListListeners.removePropertyChangeListener (listener); + } + + @Override + public String toString() { + URL loc = getLocation(); + return "LibraryManager[" + (loc != null ? loc : "default") + "]"; // NOI18N + } + + LibraryStorageArea getArea() { + return area; + } } // end LibraryManager diff -r ffef4cca2ff4 project.libraries/src/org/netbeans/spi/project/libraries/ArealLibraryProvider.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/project.libraries/src/org/netbeans/spi/project/libraries/ArealLibraryProvider.java Thu Feb 21 14:38:17 2008 +0100 @@ -0,0 +1,124 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.spi.project.libraries; + +import java.beans.PropertyChangeListener; +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Library provider which can define libraries in particular areas. + * There is no explicit method to save a library; setters on {@link LibraryImplementation} should do this. + * @param A the type of storage area used by this provider + * @param L the type of library created by this provider + * @since org.netbeans.modules.project.libraries/1 1.15 + */ +public interface ArealLibraryProvider { + + /** + * Property to fire when {@link #getOpenAreas} might have changed. + */ + String PROP_OPEN_AREAS = "openAreas"; // NOI18N + + /** + * Adds a listener to {@link #PROP_OPEN_AREAS}. + * @param listener a listener to add + */ + void addPropertyChangeListener(PropertyChangeListener listener); + + /** + * Removes a listener to {@link #PROP_OPEN_AREAS}. + * @param listener a listener to remove + */ + void removePropertyChangeListener(PropertyChangeListener listener); + + /** + * Gets the runtime type of the area used by this provider. + * @return the area type + */ + Class areaType(); + + /** + * Gets the runtime type of the libraries created by this provider. + * @return the library type + */ + Class libraryType(); + + /** + * Creates or otherwise picks a storage area interactively. + * This might actually create a fresh area, or just load an existing one, + * or even do nothing (and return null). + * The implementor is free to show a dialog here. + * @return a new or existing storage area, or null + */ + A createArea(); + + /** + * Loads a storage area (which may or may exist yet). + * @param location an abstract storage location which may or may not be recognized by this provider + * @return an area whose {@link LibraryStorageArea#getLocation} matches the provided location, + * or null if this type of location is not recognized by this provider + */ + A loadArea(URL location); + + /** + * Looks for areas which should be somehow listed as open. + * For example, a provider which refers to library areas from project metadata + * could list all areas referred to from currently open projects. + * It is not necessary to include areas recently mentioned e.g. by {@link #createArea}. + * @return a (possibly empty) collection of areas + */ + Set getOpenAreas(); + + /** + * Gets all libraries defined in a given area. + * No two libraries in this area may share a given name (as in {@link LibraryImplementation#getName}, + * though it is permitted for libraries from different areas to have the same name. + * Changes in the set of libraries defined in this area should be fired through {@link LibraryProvider#PROP_LIBRARIES}. + * Since {@link IOException} is not thrown either from this method or from {@link LibraryProvider#getLibraries}, + * it is expected that any problems loading library definitions will be logged and that those libraries will be skipped. + * @param area some storage area (which might not even exist yet, in which case the set of libraries will initially be empty) + * @return a listenable set of libraries in this area + * (it is permitted to return distinct objects from call to call on the same area, + * i.e. no caching by the implementation is necessary) + */ + LibraryProvider getLibraries(A area); + + /** + * Creates a new library. + * @param type the kind of library to make, as in {@link LibraryTypeProvider#getLibraryType} or {@link LibraryImplementation#getType} + * @param name the library name, as in {@link LibraryImplementation#getName} + * @param area the location to define the library + * @param contents initial volume contents (keys must be contained in the appropriate {@link LibraryTypeProvider#getSupportedVolumeTypes}) + * @return a new library with matching type, name, area, and contents + * @throws IOException if an error occurs creating the library definition */ + L createLibrary(String type, String name, A area, Map> contents) throws IOException; + + /** + * Deletes an existing library. + * @param library a library produced by this provider + * @throws IOException if a problem can encountered deleting the library definition + */ + void remove(L library) throws IOException; + +} diff -r ffef4cca2ff4 project.libraries/src/org/netbeans/spi/project/libraries/LibraryFactory.java --- a/project.libraries/src/org/netbeans/spi/project/libraries/LibraryFactory.java Tue Jan 22 05:32:07 2008 +0000 +++ b/project.libraries/src/org/netbeans/spi/project/libraries/LibraryFactory.java Thu Feb 21 14:38:17 2008 +0100 @@ -58,11 +58,10 @@ private LibraryFactory() { } - /** * Creates Library for LibraryImplementation * @param libraryImplementation the library SPI object - * @return Library API instance + * @return Library API instance, for which the {@link Library#getManager} will be {@link LibraryManager#getDefault} */ public static Library createLibrary (LibraryImplementation libraryImplementation) { assert libraryImplementation != null; diff -r ffef4cca2ff4 project.libraries/src/org/netbeans/spi/project/libraries/LibraryProvider.java --- a/project.libraries/src/org/netbeans/spi/project/libraries/LibraryProvider.java Tue Jan 22 05:32:07 2008 +0000 +++ b/project.libraries/src/org/netbeans/spi/project/libraries/LibraryProvider.java Thu Feb 21 14:38:17 2008 +0100 @@ -38,6 +38,7 @@ * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ + package org.netbeans.spi.project.libraries; import java.beans.PropertyChangeListener; @@ -47,29 +48,31 @@ * Library storage is a source of libraries used by LibraryManager. * LibraryManager allows existence of multiple LibraryProviders registered in * the default lookup. + * @param L the type of implementation which will be produced by this provider */ -public interface LibraryProvider { +public interface LibraryProvider { /** * Name of libraries property */ - public static final String PROP_LIBRARIES = "libraries"; //NOI18N + String PROP_LIBRARIES = "libraries"; // NOI18N /** * Returns libraries provided by the implemented provider. - * @return LibraryImplementation[] never return null, may return empty array. + * @return (possibly empty but not null) list of libraries */ - public LibraryImplementation[] getLibraries(); + L[] getLibraries(); /** * Adds property change listener, the listener is notified when the libraries changed * @param listener */ - public void addPropertyChangeListener (PropertyChangeListener listener); + void addPropertyChangeListener (PropertyChangeListener listener); /** * Removes property change listener * @param listener */ - public void removePropertyChangeListener (PropertyChangeListener listener); + void removePropertyChangeListener (PropertyChangeListener listener); + } diff -r ffef4cca2ff4 project.libraries/src/org/netbeans/spi/project/libraries/LibraryStorageArea.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/project.libraries/src/org/netbeans/spi/project/libraries/LibraryStorageArea.java Thu Feb 21 14:38:17 2008 +0100 @@ -0,0 +1,46 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (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.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.spi.project.libraries; + +import java.net.URL; + +/** + * Abstract location where zero or more libraries are defined. + * {@link Object#equals} and {@link Object#hashCode} are expected to be defined + * such that object identity (within the implementing class) are driven by {@link #getLocation}. + * @see ArealLibraryProvider + * @since org.netbeans.modules.project.libraries/1 1.15 + */ +public interface LibraryStorageArea { + + /** + * Gets an associated storage location. + * The contents of the URL (if it is even accessible) are unspecified. + * @return an associated URL uniquely identifying this location + */ + URL getLocation(); + + /** + * Gets a human-readable display label for this area. + * @return a localized display name + */ + String getDisplayName(); + +} diff -r ffef4cca2ff4 project.libraries/src/org/netbeans/spi/project/libraries/LibraryTypeProvider.java --- a/project.libraries/src/org/netbeans/spi/project/libraries/LibraryTypeProvider.java Tue Jan 22 05:32:07 2008 +0000 +++ b/project.libraries/src/org/netbeans/spi/project/libraries/LibraryTypeProvider.java Thu Feb 21 14:38:17 2008 +0100 @@ -75,6 +75,8 @@ /** * Creates a new empty library implementation. + * Generally will use {@link LibrariesSupport#createLibraryImplementation}. + * This method is not used by {@link LibraryManager#createLibrary} except in the case of {@link LibraryManager#getDefault}. * @return the created library model, never null */ public LibraryImplementation createLibrary (); @@ -85,6 +87,7 @@ * If the LibraryTypeProvider implementation requires clean of * additional settings (e.g. remove properties in the build.properties) * it should be done in this method. + * This method is not used by {@link LibraryManager#createLibrary} except in the case of {@link LibraryManager#getDefault}. * @param libraryImpl */ public void libraryDeleted (LibraryImplementation libraryImpl); @@ -96,13 +99,13 @@ * If the LibraryTypeProvider implementation requires initialization of * additional settings (e.g. adding properties into the build.properties) * it should be done in this method. - * + * This method is not used by {@link LibraryManager#createLibrary} except in the case of {@link LibraryManager#getDefault}. */ public void libraryCreated (LibraryImplementation libraryImpl); /** * Returns customizer for given volume's type, or null if the volume is not customizable. - * The object of the LibraryImplementation type is + * The object of the LibraryImplementation type and optionally LibraryStorageArea type is * passed to the customizer's setObject method. * The customized object describes the library created by this * provider, but the customizer cannot assume that the customized diff -r ffef4cca2ff4 project.libraries/src/org/netbeans/spi/project/libraries/support/LibrariesSupport.java --- a/project.libraries/src/org/netbeans/spi/project/libraries/support/LibrariesSupport.java Tue Jan 22 05:32:07 2008 +0000 +++ b/project.libraries/src/org/netbeans/spi/project/libraries/support/LibrariesSupport.java Thu Feb 21 14:38:17 2008 +0100 @@ -40,10 +40,19 @@ */ package org.netbeans.spi.project.libraries.support; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; import org.netbeans.modules.project.libraries.LibraryTypeRegistry; import org.netbeans.spi.project.libraries.LibraryImplementation; import org.netbeans.modules.project.libraries.DefaultLibraryImplementation; import org.netbeans.spi.project.libraries.LibraryTypeProvider; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.filesystems.URLMapper; +import org.openide.util.Parameters; /** * SPI Support class. @@ -85,4 +94,142 @@ public static LibraryTypeProvider[] getLibraryTypeProviders () { return LibraryTypeRegistry.getDefault().getLibraryTypeProviders(); } + + /** + * Properly converts possibly relative file to URL. + * @param f file to convert; can be relative; cannot be null + * @return url + * @since org.netbeans.modules.project.libraries/1 1.17 + */ + public static URL convertFilePathToURL(String path) { + try { + File f = new File(path); + if (f.isAbsolute()) { + return f.toURI().toURL(); + } else { + // create hierarchical relative URI (that is no schema) + // to encode OS characters + URI uri = new URI(null, null, path.replace('\\', '/'), null); + return new URL("file", null, uri.getRawPath()); + } + + } catch (URISyntaxException ex) { + IllegalArgumentException y = new IllegalArgumentException(); + y.initCause(ex); + throw y; + } catch (MalformedURLException ex) { + IllegalArgumentException y = new IllegalArgumentException(); + y.initCause(ex); + throw y; + } + } + + /** + * Properly converts possibly relative URL to file. + * @param url file URL to convert; can be relative; cannot be null + * @return url + * @since org.netbeans.modules.project.libraries/1 1.17 + */ + public static String convertURLToFilePath(URL url) { + if (!"file".equals(url.getProtocol())) { + throw new IllegalArgumentException("not file URL "+url); //NOI18N + } + try { + if (isAbsoluteURL(url)) { + return new File(new URI(url.toExternalForm())).getPath(); + } else { + // workaround to decode URL path - created fake absolute URI + // just to construct File instance and properly decoded path: + URI uri3 = new URI("file:/"+url.getPath()); + return new File(uri3).getPath().substring(1); + } + } catch (URISyntaxException ex) { + IllegalArgumentException y = new IllegalArgumentException(); + y.initCause(ex); + throw y; + } + } + + /** + * Is given URL absolute? + * + * @param url url to test; cannot be null + * @return is absolute + * @since org.netbeans.modules.project.libraries/1 1.17 + */ + public static boolean isAbsoluteURL(URL url) { + if ("jar".equals(url.getProtocol())) { // NOI18N + url = FileUtil.getArchiveFile(url); + } + return url.getPath().startsWith("/"); + } + + /** + * Helper method to resolve (possibly relative) library content URL to FileObject. + * + * @param libraryLocation library location file; can be null for global libraries + * @param libraryEntry library entry to resolve + * @return file object + * @since org.netbeans.modules.project.libraries/1 1.17 + */ + public static FileObject resolveLibraryEntryFileObject(URL libraryLocation, URL libraryEntry) { + URL u = resolveLibraryEntryURL(libraryLocation, libraryEntry); + return URLMapper.findFileObject(u); + } + + /** + * Helper method to resolve (possibly relative) library content URL. + * + * @param libraryLocation library location file; can be null for global libraries + * @param libraryEntry library entry to resolve + * @return absolute URL + * @since org.netbeans.modules.project.libraries/1 1.17 + */ + public static URL resolveLibraryEntryURL(URL libraryLocation, URL libraryEntry) { + Parameters.notNull("libraryEntry", libraryEntry); //NOI18N + if (isAbsoluteURL(libraryEntry)) { + return libraryEntry; + } else { + if (libraryLocation == null) { + throw new IllegalArgumentException("cannot resolve relative URL without library location"); //NOI18N + } + if (!"file".equals(libraryLocation.getProtocol())) { //NOI18N + throw new IllegalArgumentException("not file: protocol - "+libraryLocation.toExternalForm()); //NOI18N + } + File libLocation = new File(URI.create(libraryLocation.toExternalForm())); + if (!libLocation.getName().endsWith(".properties")) { //NOI18N + throw new IllegalArgumentException("library location must be a file - "+libraryLocation.toExternalForm()); //NOI18N + } + File libBase = libLocation.getParentFile(); + String jarFolder = null; + if ("jar".equals(libraryEntry.getProtocol())) { + assert libraryEntry.toExternalForm().indexOf("!/") != -1 : libraryEntry.toExternalForm(); //NOI18N + jarFolder = libraryEntry.toExternalForm().substring(libraryEntry.toExternalForm().indexOf("!/")+2); //NOI18N + libraryEntry = FileUtil.getArchiveFile(libraryEntry); + } + String path = convertURLToFilePath(libraryEntry); + File f = FileUtil.normalizeFile(new File(libBase, path)); + URL u; + try { + u = f.toURI().toURL(); + } catch (MalformedURLException ex) { + IllegalArgumentException y = new IllegalArgumentException(); + y.initCause(ex); + throw y; + } + if (jarFolder != null) { + u = FileUtil.getArchiveRoot(u); + try { + u = new URL(u + jarFolder.replace('\\', '/')); //NOI18N + } catch (MalformedURLException e) { + throw new AssertionError(e); + } + } + return u; + } + } + + // TODO: add method which compares two libraries: compare content and file sizes and ... + + // TODO: move some of these methods to openide.FileUtil }