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;
043:
044: import java.beans.PropertyChangeListener;
045: import java.beans.PropertyChangeSupport;
046: import java.io.File;
047: import java.io.IOException;
048: import java.io.InputStream;
049: import java.io.OutputStream;
050: import java.io.OutputStreamWriter;
051: import java.io.PrintWriter;
052: import java.net.URL;
053: import java.util.Collections;
054: import java.util.HashMap;
055: import java.util.Iterator;
056: import java.util.List;
057: import java.util.Map;
058: import java.util.Properties;
059: import java.util.ResourceBundle;
060: import java.util.Set;
061: import java.util.StringTokenizer;
062: import java.util.TreeSet;
063: import javax.xml.parsers.ParserConfigurationException;
064: import org.netbeans.spi.project.libraries.LibraryImplementation;
065: import org.netbeans.spi.project.libraries.LibraryTypeProvider;
066: import org.openide.ErrorManager;
067: import org.openide.filesystems.FileChangeAdapter;
068: import org.openide.filesystems.FileEvent;
069: import org.openide.filesystems.FileLock;
070: import org.openide.filesystems.FileObject;
071: import org.openide.filesystems.FileSystem;
072: import org.openide.filesystems.FileUtil;
073: import org.openide.filesystems.Repository;
074: import org.openide.util.NbBundle;
075: import org.openide.xml.XMLUtil;
076: import org.xml.sax.InputSource;
077: import org.xml.sax.SAXException;
078:
079: public class LibrariesStorage extends FileChangeAdapter implements
080: WritableLibraryProvider<LibraryImplementation> {
081:
082: private static final String NB_HOME_PROPERTY = "netbeans.home"; //NOI18N
083: private static final String LIBRARIES_REPOSITORY = "org-netbeans-api-project-libraries/Libraries"; //NOI18N
084: private static final String TIME_STAMPS_FILE = "libraries-timestamps.properties"; //NOI18B
085: private static final String XML_EXT = "xml"; //NOI18N
086:
087: // persistent storage, it may be null for before first library is store into storage
088: private FileObject storage = null;
089:
090: private Map<String, LibraryImplementation> libraries;
091:
092: private Map<String, LibraryImplementation> librariesByFileNames;
093:
094: // Library declaraion public ID
095: // i18n bundle
096: private ResourceBundle bundle;
097:
098: private PropertyChangeSupport support;
099:
100: //Flag if the storage is initialized
101: //The storage needs to be lazy initialized, it is in lookup
102: private boolean initialized;
103:
104: private Properties timeStamps;
105:
106: /**
107: * Create libraries that need to be populated later.
108: */
109: public LibrariesStorage() {
110: this .support = new PropertyChangeSupport(this );
111: }
112:
113: /**
114: * Constructor for tests
115: */
116: LibrariesStorage(FileObject storage) {
117: this ();
118: this .storage = storage;
119: }
120:
121: /**
122: * Initialize the default storage.
123: * @return new storage or null on I/O error.
124: */
125: private static final FileObject createStorage() {
126: FileSystem storageFS = Repository.getDefault()
127: .getDefaultFileSystem();
128: try {
129: return FileUtil.createFolder(storageFS.getRoot(),
130: LIBRARIES_REPOSITORY);
131: } catch (IOException e) {
132: return null;
133: }
134: }
135:
136: // scans over storage and fetchs it ((fileset persistence files) into memory
137: // ... note that providers can read their data during getVolume call
138: private void loadFromStorage() {
139: // configure parser
140: libraries = new HashMap<String, LibraryImplementation>();
141: librariesByFileNames = new HashMap<String, LibraryImplementation>();
142: LibraryDeclarationHandlerImpl handler = new LibraryDeclarationHandlerImpl();
143: LibraryDeclarationConvertorImpl convertor = new LibraryDeclarationConvertorImpl();
144: LibraryDeclarationParser parser = new LibraryDeclarationParser(
145: handler, convertor);
146: // parse
147: for (FileObject descriptorFile : storage.getChildren()) {
148: if (XML_EXT.equalsIgnoreCase(descriptorFile.getExt())) {
149: try {
150: handler.setLibrary(null);
151: readLibrary(descriptorFile, parser);
152: LibraryImplementation impl = handler.getLibrary();
153: if (impl != null) {
154: LibraryTypeProvider provider = LibraryTypeRegistry
155: .getDefault().getLibraryTypeProvider(
156: impl.getType());
157: if (provider == null) {
158: ErrorManager
159: .getDefault()
160: .log(
161: ErrorManager.WARNING,
162: "LibrariesStorage: Can not invoke LibraryTypeProvider.libraryCreated(), the library type provider is unknown."); //NOI18N
163: } else if (libraries.keySet().contains(
164: impl.getName())) {
165: ErrorManager
166: .getDefault()
167: .log(
168: ErrorManager.WARNING,
169: "LibrariesStorage: Library \""
170: + impl.getName()
171: + "\" is already defined, skeeping the definition from: "
172: + FileUtil
173: .getFileDisplayName(descriptorFile));
174: } else {
175: if (!isUpToDate(descriptorFile)) {
176: provider.libraryCreated(impl);
177: updateTimeStamp(descriptorFile);
178: }
179: librariesByFileNames.put(descriptorFile
180: .getPath(), impl);
181: libraries.put(impl.getName(), impl);
182: }
183: }
184: } catch (SAXException e) {
185: ErrorManager.getDefault().notify(e);
186: } catch (ParserConfigurationException e) {
187: ErrorManager.getDefault().notify(e);
188: } catch (IOException e) {
189: ErrorManager.getDefault().notify(e);
190: } catch (RuntimeException e) {
191: // Other problem.
192: ErrorManager.getDefault().notify(e);
193: }
194: }
195: }
196: try {
197: saveTimeStamps();
198: } catch (IOException ioe) {
199: ErrorManager.getDefault().notify(ioe);
200: }
201: }
202:
203: private synchronized void initStorage() {
204: if (!initialized) {
205: if (this .storage == null) {
206: this .storage = createStorage();
207: if (storage == null) {
208: // Storage broken. May happen e.g. inside unit tests.
209: libraries = Collections.emptyMap();
210: librariesByFileNames = Collections.emptyMap();
211: initialized = true;
212: return;
213: }
214: }
215: this .loadFromStorage();
216: this .storage.addFileChangeListener(this );
217: initialized = true;
218: }
219: }
220:
221: private static LibraryImplementation readLibrary(
222: FileObject descriptorFile) throws SAXException,
223: ParserConfigurationException, IOException {
224: return readLibrary(descriptorFile, (LibraryImplementation) null);
225: }
226:
227: private static LibraryImplementation readLibrary(
228: FileObject descriptorFile, LibraryImplementation impl)
229: throws SAXException, ParserConfigurationException,
230: IOException {
231: LibraryDeclarationHandlerImpl handler = new LibraryDeclarationHandlerImpl();
232: LibraryDeclarationConvertorImpl convertor = new LibraryDeclarationConvertorImpl();
233: LibraryDeclarationParser parser = new LibraryDeclarationParser(
234: handler, convertor);
235: handler.setLibrary(impl);
236: readLibrary(descriptorFile, parser);
237: return handler.getLibrary();
238: }
239:
240: private static void readLibrary(FileObject descriptorFile,
241: LibraryDeclarationParser parser) throws SAXException,
242: ParserConfigurationException, IOException {
243: URL baseURL = descriptorFile.getURL();
244: InputSource input = new InputSource(baseURL.toExternalForm());
245: input.setByteStream(descriptorFile.getInputStream()); // #33554 workaround
246: try {
247: parser.parse(input);
248: } catch (SAXException e) {
249: ErrorManager.getDefault().annotate(e, ErrorManager.UNKNOWN,
250: "From " + baseURL, null, null, null);
251: throw e;
252: }
253: }
254:
255: private void writeLibrary(final FileObject storage,
256: final LibraryImplementation library) throws IOException {
257: storage.getFileSystem().runAtomicAction(
258: new FileSystem.AtomicAction() {
259: public void run() throws IOException {
260: String libraryType = library.getType();
261: LibraryTypeProvider libraryTypeProvider = LibraryTypeRegistry
262: .getDefault().getLibraryTypeProvider(
263: libraryType);
264: if (libraryTypeProvider == null) {
265: ErrorManager
266: .getDefault()
267: .log(
268: ErrorManager.WARNING,
269: "LibrariesStorage: Cannot store library, the library type is not recognized by any of installed LibraryTypeProviders."); //NOI18N
270: return;
271: }
272: FileObject fo = storage.createData(library
273: .getName(), "xml"); //NOI18N
274: writeLibraryDefinition(fo, library,
275: libraryTypeProvider);
276: }
277: });
278: }
279:
280: private static void writeLibraryDefinition(
281: final FileObject definitionFile,
282: final LibraryImplementation library,
283: final LibraryTypeProvider libraryTypeProvider)
284: throws IOException {
285: FileLock lock = null;
286: PrintWriter out = null;
287: try {
288: lock = definitionFile.lock();
289: out = new PrintWriter(new OutputStreamWriter(definitionFile
290: .getOutputStream(lock), "UTF-8"));
291: // XXX use DOM and XMLUtil.write instead
292: out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); //NOI18N
293: out
294: .println("<!DOCTYPE library PUBLIC \"-//NetBeans//DTD Library Declaration 1.0//EN\" \"http://www.netbeans.org/dtds/library-declaration-1_0.dtd\">"); //NOI18N
295: out.println("<library version=\"1.0\">"); //NOI18N
296: out.println("\t<name>" + library.getName() + "</name>"); //NOI18N
297: out.println("\t<type>" + library.getType() + "</type>");
298: String description = library.getDescription();
299: if (description != null && description.length() > 0) {
300: out.println("\t<description>" + description
301: + "</description>"); //NOI18N
302: }
303: String localizingBundle = library.getLocalizingBundle();
304: if (localizingBundle != null
305: && localizingBundle.length() > 0) {
306: out.println("\t<localizing-bundle>"
307: + XMLUtil.toElementContent(localizingBundle)
308: + "</localizing-bundle>"); //NOI18N
309: }
310: String[] volumeTypes = libraryTypeProvider
311: .getSupportedVolumeTypes();
312: for (String vtype : volumeTypes) {
313: out.println("\t<volume>"); //NOI18N
314: out.println("\t\t<type>" + vtype + "</type>"); //NOI18N
315: List<URL> volume = library.getContent(vtype);
316: if (volume != null) {
317: //If null -> broken library, repair it.
318: for (URL url : volume) {
319: out.println("\t\t<resource>"
320: + XMLUtil.toElementContent(url
321: .toExternalForm())
322: + "</resource>"); //NOI18N
323: }
324: }
325: out.println("\t</volume>"); //NOI18N
326: }
327: out.println("</library>"); //NOI18N
328: } finally {
329: if (out != null)
330: out.close();
331: if (lock != null)
332: lock.releaseLock();
333: }
334: }
335:
336: private void fireLibrariesChanged() {
337: this .support.firePropertyChange(PROP_LIBRARIES, null, null);
338: }
339:
340: public final void addPropertyChangeListener(
341: PropertyChangeListener listener) {
342: this .support.addPropertyChangeListener(listener);
343: }
344:
345: public final void removePropertyChangeListener(
346: PropertyChangeListener listener) {
347: this .support.removePropertyChangeListener(listener);
348: }
349:
350: /**
351: * Return all libraries in memory.
352: */
353: public final LibraryImplementation[] getLibraries() {
354: this .initStorage();
355: assert this .storage != null : "Storage is not initialized";
356: return libraries.values().toArray(
357: new LibraryImplementation[libraries.size()]);
358: } // end getLibraries
359:
360: public void addLibrary(LibraryImplementation library)
361: throws IOException {
362: this .initStorage();
363: assert this .storage != null : "Storage is not initialized";
364: writeLibrary(this .storage, library);
365: }
366:
367: public void removeLibrary(LibraryImplementation library)
368: throws IOException {
369: this .initStorage();
370: assert this .storage != null : "Storage is not initialized";
371: for (String key : librariesByFileNames.keySet()) {
372: LibraryImplementation lib = this .librariesByFileNames
373: .get(key);
374: if (library.equals(lib)) {
375: FileObject fo = this .storage.getFileSystem()
376: .findResource(key);
377: if (fo != null) {
378: fo.delete();
379: return;
380: }
381: }
382: }
383: }
384:
385: public void updateLibrary(final LibraryImplementation oldLibrary,
386: final LibraryImplementation newLibrary) throws IOException {
387: this .initStorage();
388: assert this .storage != null : "Storage is not initialized";
389: for (String key : librariesByFileNames.keySet()) {
390: LibraryImplementation lib = librariesByFileNames.get(key);
391: if (oldLibrary.equals(lib)) {
392: final FileObject fo = this .storage.getFileSystem()
393: .findResource(key);
394: if (fo != null) {
395: String libraryType = newLibrary.getType();
396: final LibraryTypeProvider libraryTypeProvider = LibraryTypeRegistry
397: .getDefault().getLibraryTypeProvider(
398: libraryType);
399: if (libraryTypeProvider == null) {
400: ErrorManager
401: .getDefault()
402: .log(
403: ErrorManager.WARNING,
404: "LibrariesStorageL Cannot store library, the library type is not recognized by any of installed LibraryTypeProviders."); //NOI18N
405: return;
406: }
407: this .storage.getFileSystem().runAtomicAction(
408: new FileSystem.AtomicAction() {
409: public void run() throws IOException {
410: writeLibraryDefinition(fo,
411: newLibrary,
412: libraryTypeProvider);
413: }
414: });
415: }
416: }
417: }
418: }
419:
420: public void fileDataCreated(FileEvent fe) {
421: FileObject fo = fe.getFile();
422: try {
423: final LibraryImplementation impl = readLibrary(fo);
424: if (impl != null) {
425: LibraryTypeProvider provider = LibraryTypeRegistry
426: .getDefault().getLibraryTypeProvider(
427: impl.getType());
428: if (provider == null) {
429: ErrorManager
430: .getDefault()
431: .log(
432: ErrorManager.WARNING,
433: "LibrariesStorage: Can not invoke LibraryTypeProvider.libraryCreated(), the library type provider is unknown."); //NOI18N
434: } else {
435: synchronized (this ) {
436: this .libraries.put(impl.getName(), impl);
437: this .librariesByFileNames.put(fo.getPath(),
438: impl);
439: }
440: //Has to be called outside the synchronized block,
441: // The code is provided by LibraryType implementator and can fire events -> may cause deadlocks
442: try {
443: provider.libraryCreated(impl);
444: updateTimeStamp(fo);
445: saveTimeStamps();
446: } catch (RuntimeException e) {
447: String message = NbBundle.getMessage(
448: LibrariesStorage.class,
449: "MSG_libraryCreatedError");
450: ErrorManager.getDefault().notify(
451: ErrorManager.getDefault().annotate(e,
452: message));
453: }
454: this .fireLibrariesChanged();
455: }
456: }
457: } catch (SAXException e) {
458: ErrorManager.getDefault().notify(e);
459: } catch (ParserConfigurationException e) {
460: ErrorManager.getDefault().notify(e);
461: } catch (IOException e) {
462: ErrorManager.getDefault().notify(e);
463: }
464: }
465:
466: public void fileDeleted(FileEvent fe) {
467: String fileName = fe.getFile().getPath();
468: LibraryImplementation impl;
469: synchronized (this ) {
470: impl = this .librariesByFileNames.remove(fileName);
471: if (impl != null) {
472: this .libraries.remove(impl.getName());
473: }
474: }
475: if (impl != null) {
476: LibraryTypeProvider provider = LibraryTypeRegistry
477: .getDefault()
478: .getLibraryTypeProvider(impl.getType());
479: if (provider == null) {
480: ErrorManager
481: .getDefault()
482: .log(
483: ErrorManager.WARNING,
484: "LibrariesStorage: Cannot invoke LibraryTypeProvider.libraryDeleted(), the library type provider is unknown."); //NOI18N
485: } else {
486: //Has to be called outside the synchronized block,
487: // The code is provided by LibraryType implementator and can fire events -> may cause deadlocks
488: try {
489: provider.libraryDeleted(impl);
490: } catch (RuntimeException e) {
491: String message = NbBundle.getMessage(
492: LibrariesStorage.class,
493: "MSG_libraryDeletedError");
494: ErrorManager.getDefault().notify(
495: ErrorManager.getDefault().annotate(e,
496: message));
497: }
498: }
499: this .fireLibrariesChanged();
500: }
501: }
502:
503: public void fileChanged(FileEvent fe) {
504: FileObject definitionFile = fe.getFile();
505: String fileName = definitionFile.getPath();
506: LibraryImplementation impl;
507: synchronized (this ) {
508: impl = this .librariesByFileNames.get(fileName);
509: }
510: if (impl != null) {
511: try {
512: readLibrary(definitionFile, impl);
513: LibraryTypeProvider provider = LibraryTypeRegistry
514: .getDefault().getLibraryTypeProvider(
515: impl.getType());
516: if (provider == null) {
517: ErrorManager
518: .getDefault()
519: .log(
520: ErrorManager.WARNING,
521: "LibrariesStorage: Can not invoke LibraryTypeProvider.libraryCreated(), the library type provider is unknown."); //NOI18N
522: }
523: try {
524: //TODO: LibraryTypeProvider should be extended by libraryUpdated method
525: provider.libraryCreated(impl);
526: updateTimeStamp(definitionFile);
527: saveTimeStamps();
528: } catch (RuntimeException e) {
529: String message = NbBundle.getMessage(
530: LibrariesStorage.class,
531: "MSG_libraryCreatedError");
532: ErrorManager.getDefault().notify(
533: ErrorManager.getDefault().annotate(e,
534: message));
535: }
536: } catch (SAXException se) {
537: ErrorManager.getDefault().notify(se);
538: } catch (ParserConfigurationException pce) {
539: ErrorManager.getDefault().notify(pce);
540: } catch (IOException ioe) {
541: ErrorManager.getDefault().notify(ioe);
542: }
543: }
544: }
545:
546: protected final ResourceBundle getBundle() {
547: if (bundle == null) {
548: bundle = NbBundle.getBundle(LibrariesStorage.class);
549: }
550: return bundle;
551: }
552:
553: private boolean isUpToDate(FileObject libraryDefinition) {
554: Properties timeStamps = getTimeStamps();
555: String ts = (String) timeStamps.get(libraryDefinition
556: .getNameExt());
557: return ts == null ? false
558: : Long.parseLong(ts) >= libraryDefinition
559: .lastModified().getTime();
560: }
561:
562: private void updateTimeStamp(FileObject libraryDefinition) {
563: Properties timeStamps = getTimeStamps();
564: timeStamps.put(libraryDefinition.getNameExt(), Long
565: .toString(libraryDefinition.lastModified().getTime()));
566: }
567:
568: private void saveTimeStamps() throws IOException {
569: if (this .storage != null) {
570: Properties timeStamps = getTimeStamps();
571: if (timeStamps.get(NB_HOME_PROPERTY) == null) {
572: String currNbLoc = getNBRoots();
573: timeStamps.put(NB_HOME_PROPERTY, currNbLoc);
574: }
575: FileObject parent = storage.getParent();
576: FileObject timeStampFile = parent
577: .getFileObject(TIME_STAMPS_FILE);
578: if (timeStampFile == null) {
579: timeStampFile = parent.createData(TIME_STAMPS_FILE);
580: }
581: FileLock lock = timeStampFile.lock();
582: try {
583: OutputStream out = timeStampFile.getOutputStream(lock);
584: try {
585: timeStamps.store(out, null);
586: } finally {
587: out.close();
588: }
589: } finally {
590: lock.releaseLock();
591: }
592: }
593: }
594:
595: private Properties getTimeStamps() {
596: if (this .timeStamps == null) {
597: this .timeStamps = new Properties();
598: if (this .storage != null) {
599: FileObject timeStampFile = storage.getParent()
600: .getFileObject(TIME_STAMPS_FILE);
601: if (timeStampFile != null) {
602: try {
603: InputStream in = timeStampFile.getInputStream();
604: try {
605: this .timeStamps.load(in);
606: } finally {
607: in.close();
608: }
609: String nbLoc = (String) this .timeStamps
610: .get(NB_HOME_PROPERTY);
611: String currNbLoc = getNBRoots();
612: if (nbLoc == null || !nbLoc.equals(currNbLoc)) {
613: this .timeStamps.clear();
614: }
615: } catch (IOException ioe) {
616: ErrorManager.getDefault().notify(ioe);
617: }
618: }
619: }
620: }
621: return this .timeStamps;
622: }
623:
624: private static String getNBRoots() {
625: Set<String> result = new TreeSet<String>();
626: String currentNbLoc = System.getProperty("netbeans.home"); //NOI18N
627: if (currentNbLoc != null) {
628: File f = FileUtil.normalizeFile(new File(currentNbLoc));
629: if (f.isDirectory()) {
630: result.add(f.getAbsolutePath());
631: }
632: }
633: currentNbLoc = System.getProperty("netbeans.dirs"); //NOI18N
634: if (currentNbLoc != null) {
635: StringTokenizer tok = new StringTokenizer(currentNbLoc,
636: File.pathSeparator);
637: while (tok.hasMoreTokens()) {
638: File f = FileUtil.normalizeFile(new File(tok
639: .nextToken()));
640: result.add(f.getAbsolutePath());
641: }
642: }
643: StringBuffer sb = new StringBuffer();
644: for (Iterator<String> it = result.iterator(); it.hasNext();) {
645: sb.append(it.next());
646: if (it.hasNext()) {
647: sb.append(":"); //NOI18N
648: }
649: }
650: return sb.toString();
651: }
652:
653: }
|