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.modules.project.libraries.ui;
043:
044: import java.awt.EventQueue;
045: import java.beans.PropertyChangeEvent;
046: import java.beans.PropertyChangeListener;
047: import java.beans.PropertyChangeSupport;
048: import java.io.IOException;
049: import java.net.URL;
050: import java.util.ArrayList;
051: import java.util.Collection;
052: import java.util.Collections;
053: import java.util.Comparator;
054: import java.util.HashMap;
055: import java.util.HashSet;
056: import java.util.List;
057: import java.util.Map;
058: import java.util.Set;
059: import java.util.TreeSet;
060: import java.util.logging.Level;
061: import java.util.logging.Logger;
062: import javax.swing.event.ChangeListener;
063: import org.netbeans.modules.project.libraries.LibraryAccessor;
064: import org.netbeans.spi.project.libraries.LibraryImplementation;
065: import org.netbeans.spi.project.libraries.LibraryProvider;
066: import org.netbeans.modules.project.libraries.WritableLibraryProvider;
067: import org.netbeans.spi.project.libraries.ArealLibraryProvider;
068: import org.netbeans.spi.project.libraries.LibraryStorageArea;
069: import org.openide.util.Lookup;
070: import org.openide.util.ChangeSupport;
071: import org.openide.util.NbBundle;
072: import org.openide.util.WeakListeners;
073:
074: public class LibrariesModel implements PropertyChangeListener {
075:
076: public static final LibraryStorageArea GLOBAL_AREA = new LibraryStorageArea() {
077: public String getDisplayName() {
078: return NbBundle.getMessage(LibrariesModel.class,
079: "LBL_global");
080: }
081:
082: public URL getLocation() {
083: throw new AssertionError();
084: }
085: };
086:
087: private static final Logger LOG = Logger
088: .getLogger(LibrariesModel.class.getName());
089:
090: /**
091: * Set of areas which have been explicitly created/loaded in this IDE session (thus static).
092: * Keep only URL, <em>not</em> LibraryStorageArea, to avoid memory leaks.
093: * Could also be modified to persist a LRU in NbPreferences, etc.
094: */
095: public static final Set<URL> createdAreas = Collections
096: .synchronizedSet(new HashSet<URL>());
097:
098: private final Map<LibraryImplementation, LibraryStorageArea> library2Area = new HashMap<LibraryImplementation, LibraryStorageArea>();
099: private final Map<LibraryStorageArea, ArealLibraryProvider> area2Storage = new HashMap<LibraryStorageArea, ArealLibraryProvider>();
100: private final Map<LibraryImplementation, LibraryProvider> storageByLib = new HashMap<LibraryImplementation, LibraryProvider>();
101: private final Map<LibraryStorageArea, LibraryProvider> area2Provider = new HashMap<LibraryStorageArea, LibraryProvider>();
102: private final Collection<LibraryImplementation> actualLibraries = new TreeSet<LibraryImplementation>(
103: new LibrariesComparator());
104: private final List<LibraryImplementation> addedLibraries;
105: private final List<LibraryImplementation> removedLibraries;
106: private final List<ProxyLibraryImplementation> changedLibraries;
107: private WritableLibraryProvider writableProvider;
108: private final ChangeSupport cs = new ChangeSupport(this );
109:
110: public LibrariesModel() {
111: this .addedLibraries = new ArrayList<LibraryImplementation>();
112: this .removedLibraries = new ArrayList<LibraryImplementation>();
113: this .changedLibraries = new ArrayList<ProxyLibraryImplementation>();
114: for (LibraryProvider lp : Lookup.getDefault().lookupAll(
115: LibraryProvider.class)) {
116: lp.addPropertyChangeListener(WeakListeners.propertyChange(
117: this , lp));
118: if (writableProvider == null
119: && lp instanceof WritableLibraryProvider) {
120: writableProvider = (WritableLibraryProvider) lp;
121: }
122: }
123: for (ArealLibraryProvider alp : Lookup.getDefault().lookupAll(
124: ArealLibraryProvider.class)) {
125: alp.addPropertyChangeListener(WeakListeners.propertyChange(
126: this , alp));
127: }
128: this .computeLibraries();
129: }
130:
131: public synchronized Collection<? extends LibraryImplementation> getLibraries() {
132: return actualLibraries;
133: }
134:
135: public void addChangeListener(ChangeListener l) {
136: cs.addChangeListener(l);
137: }
138:
139: public void removeChangeListener(ChangeListener l) {
140: cs.removeChangeListener(l);
141: }
142:
143: public LibraryStorageArea createArea() {
144: for (ArealLibraryProvider alp : Lookup.getDefault().lookupAll(
145: ArealLibraryProvider.class)) {
146: LibraryStorageArea area = alp.createArea();
147: if (area != null) {
148: createdAreas.add(area.getLocation());
149: area2Storage.put(area, alp);
150: propertyChange(null); // recompute libraries & fire change
151: return area;
152: }
153: }
154: return null;
155: }
156:
157: public LibraryImplementation createArealLibrary(String type,
158: String name, LibraryStorageArea area) {
159: LibraryImplementation impl = new DummyArealLibrary(type, name);
160: library2Area.put(impl, area);
161: return impl;
162: }
163:
164: public Collection<? extends LibraryStorageArea> getAreas() {
165: Set<LibraryStorageArea> areas = new HashSet<LibraryStorageArea>();
166: for (ArealLibraryProvider alp : Lookup.getDefault().lookupAll(
167: ArealLibraryProvider.class)) {
168: for (LibraryStorageArea area : LibraryAccessor
169: .getOpenAreas(alp)) {
170: area2Storage.put(area, alp);
171: areas.add(area);
172: }
173: }
174: for (ArealLibraryProvider alp : Lookup.getDefault().lookupAll(
175: ArealLibraryProvider.class)) {
176: for (URL location : createdAreas) {
177: LibraryStorageArea area = alp.loadArea(location);
178: if (area != null) {
179: assert area.getLocation().equals(location) : "Bad location "
180: + area.getLocation()
181: + " does not match "
182: + location
183: + " from "
184: + alp.getClass().getName();
185: area2Storage.put(area, alp);
186: areas.add(area);
187: }
188: }
189: }
190: return areas;
191: }
192:
193: public LibraryStorageArea getArea(LibraryImplementation library) {
194: LibraryStorageArea area = getAreaOrNull(library);
195: return area != null ? area : GLOBAL_AREA;
196: }
197:
198: private LibraryStorageArea getAreaOrNull(
199: LibraryImplementation library) {
200: if (library instanceof ProxyLibraryImplementation) {
201: library = ((ProxyLibraryImplementation) library)
202: .getOriginal();
203: }
204: return library2Area.get(library);
205: }
206:
207: public void addLibrary(LibraryImplementation impl) {
208: synchronized (this ) {
209: addedLibraries.add(impl);
210: actualLibraries.add(impl);
211: }
212: cs.fireChange();
213: }
214:
215: public void removeLibrary(LibraryImplementation impl) {
216: synchronized (this ) {
217: if (addedLibraries.contains(impl)) {
218: addedLibraries.remove(impl);
219: } else {
220: removedLibraries
221: .add(((ProxyLibraryImplementation) impl)
222: .getOriginal());
223: }
224: actualLibraries.remove(impl);
225: }
226: cs.fireChange();
227: }
228:
229: public void modifyLibrary(ProxyLibraryImplementation impl) {
230: synchronized (this ) {
231: if (!addedLibraries.contains(impl)
232: && !changedLibraries.contains(impl)) {
233: changedLibraries.add(impl);
234: }
235: }
236: cs.fireChange();
237: }
238:
239: public boolean isLibraryEditable(LibraryImplementation impl) {
240: if (this .addedLibraries.contains(impl))
241: return true;
242: LibraryProvider provider = storageByLib
243: .get(((ProxyLibraryImplementation) impl).getOriginal());
244: return provider == writableProvider
245: || getAreaOrNull(impl) != null;
246: }
247:
248: public void apply() throws IOException {
249: for (LibraryImplementation impl : removedLibraries) {
250: LibraryProvider storage = storageByLib.get(impl);
251: if (storage == this .writableProvider) {
252: this .writableProvider.removeLibrary(impl);
253: } else {
254: LibraryStorageArea area = getAreaOrNull(impl);
255: if (area != null) {
256: LibraryAccessor
257: .remove(area2Storage.get(area), impl);
258: } else {
259: throw new IOException(
260: "Cannot find storage for library: "
261: + impl.getName()); // NOI18N
262: }
263: }
264: }
265: for (LibraryImplementation impl : addedLibraries) {
266: LibraryStorageArea area = getAreaOrNull(impl);
267: if (area != null) {
268: ArealLibraryProvider alp = area2Storage.get(area);
269: assert alp != null : area;
270: LibraryAccessor.createLibrary(alp, impl.getType(), impl
271: .getName(), area,
272: ((DummyArealLibrary) impl).contents);
273: } else if (writableProvider != null) {
274: writableProvider.addLibrary(impl);
275: } else {
276: throw new IOException(
277: "Cannot add libraries, no WritableLibraryProvider."); // NOI18N
278: }
279: }
280: for (ProxyLibraryImplementation proxy : changedLibraries) {
281: LibraryImplementation orig = proxy.getOriginal();
282: LibraryProvider storage = storageByLib.get(orig);
283: if (storage == this .writableProvider) {
284: this .writableProvider.updateLibrary(orig, proxy);
285: } else {
286: LibraryStorageArea area = library2Area.get(orig);
287: if (area != null) {
288: if (proxy.newContents != null) {
289: for (Map.Entry<String, List<URL>> entry : proxy.newContents
290: .entrySet()) {
291: orig.setContent(entry.getKey(), entry
292: .getValue());
293: }
294: }
295: } else {
296: throw new IOException(
297: "Cannot find storage for library: "
298: + orig.getName()); // NOI18N
299: }
300: }
301: }
302: }
303:
304: public void propertyChange(PropertyChangeEvent evt) {
305: // compute libraries later in AWT thread and not in calling thread
306: // to prevent deadlocks
307: EventQueue.invokeLater(new Runnable() {
308: public void run() {
309: computeLibraries();
310: cs.fireChange();
311: }
312: });
313: }
314:
315: private ProxyLibraryImplementation findModified(
316: LibraryImplementation impl) {
317: for (ProxyLibraryImplementation proxy : changedLibraries) {
318: if (proxy.getOriginal().equals(impl)) {
319: return proxy;
320: }
321: }
322: return null;
323: }
324:
325: private synchronized void computeLibraries() {
326: actualLibraries.clear();
327: for (LibraryProvider storage : Lookup.getDefault().lookupAll(
328: LibraryProvider.class)) {
329: for (LibraryImplementation lib : storage.getLibraries()) {
330: ProxyLibraryImplementation proxy = findModified(lib);
331: if (proxy != null) {
332: actualLibraries.add(proxy);
333: } else {
334: actualLibraries
335: .add(proxy = new ProxyLibraryImplementation(
336: lib, this ));
337: }
338: storageByLib.put(lib, storage);
339: LOG
340: .log(
341: Level.FINER,
342: "computeLibraries: storage={0} lib={1} proxy={2}",
343: new Object[] { storage, lib, proxy });
344: }
345: }
346: for (LibraryStorageArea area : getAreas()) {
347: ArealLibraryProvider alp = area2Storage.get(area);
348: assert alp != null : area;
349: LibraryProvider prov = area2Provider.get(area);
350: if (prov == null) {
351: prov = LibraryAccessor.getLibraries(alp, area);
352: prov.addPropertyChangeListener(this ); // need not be weak, we just created the source
353: area2Provider.put(area, prov);
354: }
355: for (LibraryImplementation lib : prov.getLibraries()) {
356: ProxyLibraryImplementation proxy = findModified(lib);
357: if (proxy != null) {
358: actualLibraries.add(proxy);
359: } else {
360: actualLibraries
361: .add(proxy = new ProxyLibraryImplementation(
362: lib, this ));
363: }
364: library2Area.put(lib, area);
365: LOG
366: .log(
367: Level.FINER,
368: "computeLibraries: alp={0} area={1} lib={2} proxy={3}",
369: new Object[] { alp, area, lib, proxy });
370: }
371: }
372: actualLibraries.addAll(addedLibraries);
373: LOG
374: .log(
375: Level.FINE,
376: "computeLibraries: actualLibraries={0} library2Area={1}",
377: new Object[] { actualLibraries, library2Area });
378: }
379:
380: private static class LibrariesComparator implements
381: Comparator<LibraryImplementation> {
382: public int compare(LibraryImplementation lib1,
383: LibraryImplementation lib2) {
384: String name1 = LibrariesCustomizer.getLocalizedString(lib1
385: .getLocalizingBundle(), lib1.getName());
386: String name2 = LibrariesCustomizer.getLocalizedString(lib2
387: .getLocalizingBundle(), lib2.getName());
388: int r = name1.compareToIgnoreCase(name2);
389: return r != 0 ? r : System.identityHashCode(lib1)
390: - System.identityHashCode(lib2);
391: }
392: }
393:
394: private static final class DummyArealLibrary implements
395: LibraryImplementation {
396:
397: private final String type, name;
398: final Map<String, List<URL>> contents = new HashMap<String, List<URL>>();
399: private final PropertyChangeSupport pcs = new PropertyChangeSupport(
400: this );
401:
402: public DummyArealLibrary(String type, String name) {
403: this .type = type;
404: this .name = name;
405: }
406:
407: public String getType() {
408: return type;
409: }
410:
411: public String getName() {
412: return name;
413: }
414:
415: public String getDescription() {
416: return null;
417: }
418:
419: public String getLocalizingBundle() {
420: return null;
421: }
422:
423: public List<URL> getContent(String volumeType)
424: throws IllegalArgumentException {
425: List<URL> content = contents.get(volumeType);
426: if (content != null) {
427: return content;
428: } else {
429: return Collections.emptyList();
430: }
431: }
432:
433: public void setName(String name) {
434: throw new UnsupportedOperationException();
435: }
436:
437: public void setDescription(String text) {
438: throw new UnsupportedOperationException();
439: }
440:
441: public void setLocalizingBundle(String resourceName) {
442: throw new UnsupportedOperationException();
443: }
444:
445: public void addPropertyChangeListener(PropertyChangeListener l) {
446: pcs.addPropertyChangeListener(l);
447: }
448:
449: public void removePropertyChangeListener(
450: PropertyChangeListener l) {
451: pcs.removePropertyChangeListener(l);
452: }
453:
454: public void setContent(String volumeType, List<URL> path)
455: throws IllegalArgumentException {
456: contents.put(volumeType, path);
457: pcs.firePropertyChange(LibraryImplementation.PROP_CONTENT,
458: null, null);
459: }
460:
461: @Override
462: public String toString() {
463: return "DummyArealLibrary[" + name + "]"; // NOI18N
464: }
465:
466: }
467:
468: }
|