001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.api.project.libraries;
043:
044: import java.beans.PropertyChangeSupport;
045: import java.beans.PropertyChangeListener;
046: import java.beans.PropertyChangeEvent;
047: import java.io.IOException;
048: import java.net.URL;
049: import java.util.ArrayList;
050: import java.util.Collection;
051: import java.util.HashSet;
052: import java.util.List;
053: import java.util.Map;
054: import java.util.Set;
055: import org.netbeans.modules.project.libraries.LibraryAccessor;
056: import org.netbeans.modules.project.libraries.WritableLibraryProvider;
057: import org.netbeans.modules.project.libraries.ui.LibrariesModel;
058: import org.netbeans.spi.project.libraries.ArealLibraryProvider;
059: import org.netbeans.spi.project.libraries.LibraryImplementation;
060: import org.netbeans.spi.project.libraries.LibraryProvider;
061: import org.netbeans.spi.project.libraries.LibraryStorageArea;
062: import org.netbeans.spi.project.libraries.LibraryTypeProvider;
063: import org.netbeans.spi.project.libraries.support.LibrariesSupport;
064: import org.openide.util.Lookup;
065: import org.openide.util.LookupListener;
066: import org.openide.util.LookupEvent;
067:
068: /**
069: * LibraryManager provides registry of the installed libraries.
070: * LibraryManager can be used to list all installed libraries or to
071: * query library by its system name.
072: */
073: public final class LibraryManager {
074:
075: /**
076: * Property fired when the set of libraries changes.
077: */
078: public static final String PROP_LIBRARIES = "libraries"; //NOI18N
079:
080: private static LibraryManager instance;
081:
082: private Lookup.Result<LibraryProvider> result;
083: private final Collection<LibraryProvider> currentStorages = new ArrayList<LibraryProvider>();
084: private final PropertyChangeListener plistener = new PropertyChangeListener() {
085: public void propertyChange(PropertyChangeEvent evt) {
086: if (LibraryProvider.PROP_LIBRARIES.equals(evt
087: .getPropertyName())) {
088: resetCache();
089: }
090: }
091: };
092: private final PropertyChangeSupport listeners = new PropertyChangeSupport(
093: this );
094: private static final PropertyChangeSupport openLibraryManagerListListeners = new PropertyChangeSupport(
095: LibraryManager.class);
096: private static final PropertyChangeListener AREAL_LIBRARY_PROVIDER_LISTENER = new PropertyChangeListener() {
097: public void propertyChange(PropertyChangeEvent evt) {
098: openLibraryManagerListListeners.firePropertyChange(
099: PROP_OPEN_LIBRARY_MANAGERS, null, null);
100: }
101: };
102:
103: /** Property fired when list of open library managers changes. */
104: public static final String PROP_OPEN_LIBRARY_MANAGERS = "openManagers"; // NOI18N
105: private static Lookup.Result<ArealLibraryProvider> areaProvidersLookupResult = null;
106: private static Collection<? extends ArealLibraryProvider> currentAreaProviders = new ArrayList<ArealLibraryProvider>();
107:
108: private Collection<Library> cache;
109: /** null for default manager */
110: private final ArealLibraryProvider alp;
111: /** null for default manager */
112: private final LibraryStorageArea area;
113:
114: private LibraryManager() {
115: alp = null;
116: area = null;
117: }
118:
119: private LibraryManager(ArealLibraryProvider alp,
120: LibraryStorageArea area) {
121: this .alp = alp;
122: this .area = area;
123: LibraryProvider lp = LibraryAccessor.getLibraries(alp, area);
124: lp.addPropertyChangeListener(plistener);
125: currentStorages.add(lp);
126: }
127:
128: /**
129: * Gets a human-readable description of this manager.
130: * This may be used to visually differentiate the global manager from various local managers.
131: * @return a localized display name
132: * @see LibraryStorageArea#getDisplayName
133: * @since org.netbeans.modules.project.libraries/1 1.15
134: */
135: public String getDisplayName() {
136: if (area == null) {
137: return LibrariesModel.GLOBAL_AREA.getDisplayName();
138: } else {
139: return area.getDisplayName();
140: }
141: }
142:
143: /**
144: * Gets the location associated with this manager.
145: * @return a location where library definitions are kept, or null in the case of {@link #getDefault}
146: * @see LibraryStorageArea#getLocation
147: * @see #forLocation
148: * @since org.netbeans.modules.project.libraries/1 1.15
149: */
150: public URL getLocation() {
151: return area != null ? area.getLocation() : null;
152: }
153:
154: /**
155: * Returns library by its name.
156: * @param name of the library, must not be null
157: * @return library or null if the library is not found
158: */
159: public Library getLibrary(String name) {
160: assert name != null;
161: Library[] libs = this .getLibraries();
162: for (int i = 0; i < libs.length; i++) {
163: if (name.equals(libs[i].getName())) {
164: return libs[i];
165: }
166: }
167: return null;
168: }
169:
170: /**
171: * Lists all libraries defined in this manager.
172: * @return library definitions (never <code>null</code>)
173: */
174: public synchronized Library[] getLibraries() {
175: if (this .cache == null) {
176: List<Library> l = new ArrayList<Library>();
177: if (area == null) {
178: if (result == null) {
179: result = Lookup.getDefault().lookupResult(
180: LibraryProvider.class);
181: result.addLookupListener(new LookupListener() {
182: public void resultChanged(LookupEvent ev) {
183: resetCache();
184: }
185: });
186: }
187: Collection<? extends LibraryProvider> instances = result
188: .allInstances();
189: Collection<LibraryProvider> added = new HashSet<LibraryProvider>(
190: instances);
191: added.removeAll(currentStorages);
192: Collection<LibraryProvider> removed = new HashSet<LibraryProvider>(
193: currentStorages);
194: removed.removeAll(instances);
195: currentStorages.clear();
196: for (LibraryProvider storage : instances) {
197: this .currentStorages.add(storage);
198: for (LibraryImplementation impl : storage
199: .getLibraries()) {
200: l.add(new Library(impl, this ));
201: }
202: }
203: for (LibraryProvider p : removed) {
204: p.removePropertyChangeListener(this .plistener);
205: }
206: for (LibraryProvider p : added) {
207: p.addPropertyChangeListener(this .plistener);
208: }
209: } else {
210: for (LibraryImplementation impl : currentStorages
211: .iterator().next().getLibraries()) {
212: l.add(new Library(impl, this ));
213: }
214: }
215: this .cache = l;
216: }
217: return this .cache.toArray(new Library[this .cache.size()]);
218: }
219:
220: /**
221: * Installs a new library into the library manager.
222: * <div class="nonnormative">
223: * <p>
224: * A typical usage would be:
225: * </p>
226: * LibraryManager libraryManager = LibraryManager.getDefault();
227: * LibraryImplementation libImpl = LibrariesSupport.getLibraryTypeProvider("j2se").createLibrary();
228: * libImpl.setName("FooLibTest");
229: * libImpl.setContent ("classpath",listOfResources);
230: * libraryManager.addLibrary(LibraryFactory.createLibrary(libImpl));
231: * </div>
232: * @param library to be installed, the library has to be created
233: * with registered {@link org.netbeans.spi.project.libraries.LibraryTypeProvider}.
234: * @throws IOException when the library cannot be stored
235: * @throws IllegalArgumentException if the library is not recognized by any
236: * {@link org.netbeans.spi.project.libraries.LibraryTypeProvider} or the library
237: * of the same name already exists, or if this manager is not {@link #getDefault}.
238: * @since org.netbeans.modules.project.libraries/1 1.14
239: * @deprecated Use {@link #createLibrary} instead, as this properly supports local managers.
240: */
241: @Deprecated
242: public void addLibrary(final Library library) throws IOException,
243: IllegalArgumentException {
244: assert library != null;
245: if (LibrariesSupport.getLibraryTypeProvider(library.getType()) == null) {
246: throw new IllegalArgumentException(
247: "Trying to add a library of unknown type: "
248: + library.getType()); //NOI18N
249: }
250: String newLibraryName = library.getName();
251: if (newLibraryName == null
252: || getLibrary(newLibraryName) != null) {
253: throw new IllegalArgumentException(
254: "Library hasn't name or the name is already used: "
255: + newLibraryName); //NOI18N
256: }
257: final Collection<? extends WritableLibraryProvider> providers = Lookup
258: .getDefault().lookupAll(WritableLibraryProvider.class);
259: assert providers.size() == 1;
260: providers.iterator().next().addLibrary(
261: library.getLibraryImplementation());
262: }
263:
264: /**
265: * Creates a new library definition and adds it to the list.
266: * @param type the type of library, as in {@link LibraryTypeProvider#getLibraryType} or {@link LibraryImplementation#getType}
267: * @param name the identifying name of the new library (must not duplicate a name already in use by a library in this manager)
268: * @param contents the initial contents of the library's volumes, as a map from volume type to volume content
269: * @return a newly created library
270: * @throws IOException if the new definition could not be stored
271: * @throws IllegalArgumentException if the library type or one of the content volume types is not supported,
272: * or if a library of the same name already exists in this manager
273: * @see ArealLibraryProvider#createLibrary
274: * @since org.netbeans.modules.project.libraries/1 1.15
275: */
276: public Library createLibrary(String type, String name,
277: Map<String, List<URL>> contents) throws IOException {
278: if (getLibrary(name) != null) {
279: throw new IllegalArgumentException("Name already in use: "
280: + name); // NOI18N
281: }
282: LibraryImplementation impl;
283: if (area == null) {
284: LibraryTypeProvider ltp = LibrariesSupport
285: .getLibraryTypeProvider(type);
286: if (ltp == null) {
287: throw new IllegalArgumentException(
288: "Trying to add a library of unknown type: "
289: + type); // NOI18N
290: }
291: impl = ltp.createLibrary();
292: impl.setName(name);
293: for (Map.Entry<String, List<URL>> entry : contents
294: .entrySet()) {
295: impl.setContent(entry.getKey(), entry.getValue());
296: }
297: Lookup.getDefault().lookup(WritableLibraryProvider.class)
298: .addLibrary(impl);
299: } else {
300: impl = LibraryAccessor.createLibrary(alp, type, name, area,
301: contents);
302: }
303: return new Library(impl, this );
304: }
305:
306: /**
307: * Removes installed library
308: * @param library to be removed.
309: * @throws IOException when library cannot be deleted.
310: * @throws IllegalArgumentException when library is not installed in a writable
311: * {@link org.netbeans.spi.project.libraries.LibraryProvider}
312: * @since org.netbeans.modules.project.libraries/1 1.14
313: */
314: public void removeLibrary(final Library library)
315: throws IOException, IllegalArgumentException {
316: assert library != null;
317: if (area == null) {
318: final Collection<? extends WritableLibraryProvider> providers = Lookup
319: .getDefault().lookupAll(
320: WritableLibraryProvider.class);
321: assert providers.size() == 1;
322: providers.iterator().next().removeLibrary(
323: library.getLibraryImplementation());
324: } else {
325: LibraryAccessor.remove(alp, library
326: .getLibraryImplementation());
327: }
328: }
329:
330: /**
331: * Adds PropertyChangeListener.
332: * The listener is notified when library is added or removed.
333: * @param listener to be notified
334: */
335: public synchronized void addPropertyChangeListener(
336: PropertyChangeListener listener) {
337: assert listener != null;
338: this .listeners.addPropertyChangeListener(listener);
339: }
340:
341: /**
342: * Removes PropertyChangeListener
343: * @param listener
344: */
345: public void removePropertyChangeListener(
346: PropertyChangeListener listener) {
347: assert listener != null;
348: this .listeners.removePropertyChangeListener(listener);
349: }
350:
351: private synchronized void resetCache() {
352: this .cache = null;
353: this .listeners.firePropertyChange(PROP_LIBRARIES, null, null);
354: }
355:
356: /**
357: * Get the default instance of the library manager.
358: * @return the singleton instance
359: */
360: public static synchronized LibraryManager getDefault() {
361: if (instance == null) {
362: instance = new LibraryManager();
363: }
364: return instance;
365: }
366:
367: /**
368: * Gets a library manager which loads library definitions from a particular location.
369: * There is no guarantee that the return value is the same object from call to call with the same location.
370: * @param location any storage location supported by an installed provider
371: * @return a library manager whose {@link #getLocation} matches the supplied location
372: * @throws IllegalArgumentException if no installed provider is able to manage locations of this kind
373: * @see ArealLibraryProvider#loadArea
374: * @see ArealLibraryProvider#getLibraries
375: * @since org.netbeans.modules.project.libraries/1 1.15
376: */
377: public static LibraryManager forLocation(URL location)
378: throws IllegalArgumentException {
379: for (ArealLibraryProvider alp : Lookup.getDefault().lookupAll(
380: ArealLibraryProvider.class)) {
381: LibraryStorageArea area = alp.loadArea(location);
382: if (area != null) {
383: return new LibraryManager(alp, area);
384: }
385: }
386: throw new IllegalArgumentException(location.toExternalForm());
387: }
388:
389: /**
390: * Gets an unspecified collection of managers which are somehow to be represented as open.
391: * For example, library storages referred to from open projects might be returned.
392: * You can listen on changes in list of open managers via {@link #addOpenManagersPropertyChangeListener}.
393: * There is no guarantee that the non-default managers are the same objects from call to call
394: * even if the locations remain the same.
395: * @see ArealLibraryProvider#getOpenAreas
396: * @return a set of managers, always including at least {@link #getDefault}
397: * @since org.netbeans.modules.project.libraries/1 1.15
398: */
399: public static Collection<LibraryManager> getOpenManagers() {
400: List<LibraryManager> managers = new ArrayList<LibraryManager>();
401: managers.add(getDefault());
402: Set<URL> locations = new HashSet<URL>();
403: for (ArealLibraryProvider alp : Lookup.getDefault().lookupAll(
404: ArealLibraryProvider.class)) {
405: for (LibraryStorageArea area : LibraryAccessor
406: .getOpenAreas(alp)) {
407: if (locations.add(area.getLocation())) {
408: managers.add(new LibraryManager(alp, area));
409: }
410: }
411: }
412: for (ArealLibraryProvider alp : Lookup.getDefault().lookupAll(
413: ArealLibraryProvider.class)) {
414: for (URL location : LibrariesModel.createdAreas) {
415: LibraryStorageArea area = alp.loadArea(location);
416: if (area != null) {
417: assert area.getLocation().equals(location) : "Bad location "
418: + area.getLocation()
419: + " does not match "
420: + location
421: + " from "
422: + alp.getClass().getName();
423: if (locations.add(location)) {
424: managers.add(new LibraryManager(alp, area));
425: }
426: }
427: }
428: }
429: return managers;
430: }
431:
432: /**
433: * Adds PropertyChangeListener on list of open library managers.
434: * The listener is notified when list of open library managers changes via
435: * {@link #PROP_OPEN_LIBRARY_MANAGERS}.
436: * @param listener to be notified
437: */
438: public static synchronized void addOpenManagersPropertyChangeListener(
439: PropertyChangeListener listener) {
440: assert listener != null;
441: if (areaProvidersLookupResult == null) {
442: areaProvidersLookupResult = Lookup.getDefault()
443: .lookupResult(ArealLibraryProvider.class);
444: attachListeners(areaProvidersLookupResult.allInstances());
445: areaProvidersLookupResult
446: .addLookupListener(new LookupListener() {
447: public void resultChanged(LookupEvent ev) {
448: attachListeners(areaProvidersLookupResult
449: .allInstances());
450: }
451: });
452: }
453: openLibraryManagerListListeners
454: .addPropertyChangeListener(listener);
455: }
456:
457: private static synchronized void attachListeners(
458: Collection<? extends ArealLibraryProvider> currentProviders) {
459: for (ArealLibraryProvider provider : currentAreaProviders) {
460: provider
461: .removePropertyChangeListener(AREAL_LIBRARY_PROVIDER_LISTENER);
462: }
463: for (ArealLibraryProvider provider : currentProviders) {
464: provider
465: .addPropertyChangeListener(AREAL_LIBRARY_PROVIDER_LISTENER);
466: }
467: currentAreaProviders = currentProviders;
468: }
469:
470: /**
471: * Removes PropertyChangeListener
472: * @param listener
473: */
474: public static void removeOpenManagersPropertyChangeListener(
475: PropertyChangeListener listener) {
476: assert listener != null;
477: openLibraryManagerListListeners
478: .removePropertyChangeListener(listener);
479: }
480:
481: @Override
482: public String toString() {
483: URL loc = getLocation();
484: return "LibraryManager[" + (loc != null ? loc : "default")
485: + "]"; // NOI18N
486: }
487:
488: LibraryStorageArea getArea() {
489: return area;
490: }
491:
492: } // end LibraryManager
|