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-2007 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.visualweb.complib;
043:
044: import java.io.BufferedInputStream;
045: import java.io.BufferedOutputStream;
046: import java.io.File;
047: import java.io.FileInputStream;
048: import java.io.FileNotFoundException;
049: import java.io.FileOutputStream;
050: import java.io.IOException;
051: import java.util.HashSet;
052: import java.util.Set;
053: import java.util.WeakHashMap;
054:
055: import org.netbeans.api.project.Project;
056: import org.netbeans.modules.visualweb.complib.api.ComplibException;
057: import org.openide.filesystems.FileObject;
058: import org.openide.filesystems.FileUtil;
059: import org.w3c.dom.Comment;
060: import org.w3c.dom.Document;
061: import org.w3c.dom.Element;
062: import org.w3c.dom.NodeList;
063:
064: /**
065: * This class handles the persistence of complibs in different installation scopes. Conceptually
066: * there is a USER scope, a SYSTEM scope (to be implemented), and a scope for each project.
067: * Maintains sets of ExtensionComplib-s per scope and also maintains a list of directories in an XML
068: * file.
069: *
070: * @author Edwin Goei
071: */
072: class Scope {
073: /** Relative path from project lib directory */
074: private static final String PATH_PROJECT_SCOPE_COMPLIB = "complibs"; // NOI18N
075:
076: private static final String LIBRARY_INDEX_FILENAME = "index.xml"; // NOI18N
077:
078: private static final String DIRECTORY_ELEMENT = "directory"; // NOI18N
079:
080: private static final String DIRECTORY_NAME_ATTR = "name"; // NOI18N
081:
082: private static final String VALID_DIRECTORY_LIST = "valid-directory-list"; // NOI18N
083:
084: /** Cache which maps installHome to Scope objects */
085: private static WeakHashMap<File, Scope> registry = new WeakHashMap<File, Scope>();
086:
087: /** The directory containing an index file and all expanded complibs */
088: private final File installHome;
089:
090: /** Set of String directory names in this scope */
091: private HashSet<String> directorySet = new HashSet<String>();
092:
093: /** Set of ExtensionComplib-s in this scope */
094: private HashSet<ExtensionComplib> complibSet = new HashSet<ExtensionComplib>();
095:
096: /**
097: * @param installHome
098: * directory that contains index file and all expanded complib dirs in this scope
099: */
100: private Scope(File installHome) {
101: this .installHome = installHome;
102: loadComplibs();
103: }
104:
105: static Scope createScope(File installHome) {
106: return new Scope(installHome);
107: }
108:
109: /**
110: * Factory method to get a unique Scope object for a Project
111: *
112: * @param project
113: * @return
114: * @throws IOException
115: */
116: public static Scope getScopeForProject(Project project)
117: throws IOException {
118: File installHome = getProjectInstallHome(project);
119:
120: Scope scope = (Scope) registry.get(installHome);
121: if (scope == null) {
122: scope = new Scope(installHome);
123: registry.put(installHome, scope);
124: }
125: return scope;
126: }
127:
128: public static void destroyScopeForProject(Project project)
129: throws IOException {
130: File installHome = getProjectInstallHome(project);
131: registry.remove(installHome);
132: }
133:
134: private static File getProjectInstallHome(Project project)
135: throws IOException {
136: File projectLibDir = IdeUtil
137: .getProjectLibraryDirectory(project);
138: File installHome = new File(projectLibDir,
139: PATH_PROJECT_SCOPE_COMPLIB);
140: return installHome;
141: }
142:
143: /**
144: * Load in any expanded complibs in this scope
145: */
146: private void loadComplibs() {
147: Set<String> dirs = readDirectoryIndex();
148:
149: // Restore the valid list of ExtensionComplib-s
150: FileObject complibFo = FileUtil.toFileObject(installHome);
151: if (complibFo == null) {
152: // Directory does not exist => no complibs
153: return;
154: }
155:
156: for (FileObject fo : complibFo.getChildren()) {
157: File absFile = FileUtil.toFile(fo);
158: String fileName = absFile.getName();
159:
160: // Skip the directory index file itself
161: if (LIBRARY_INDEX_FILENAME.equals(fileName)) {
162: continue;
163: }
164:
165: if (dirs.contains(fileName)) {
166: // Valid library
167: ExtensionComplib complib;
168: try {
169: complib = new ExtensionComplib(absFile);
170: } catch (Exception e) {
171: // Unable to init existing library, warn, cleanup, and skip
172: IdeUtil.logWarning(e);
173:
174: // Clean up index and persist the info
175: dirs.remove(fileName);
176: try {
177: persistDirectoryIndex();
178: } catch (Exception e2) {
179: // Cleanup failed but it is safe to ignore
180: }
181:
182: continue;
183: }
184: complibSet.add(complib);
185: } else {
186: // Clean up old unused libraries from previous runs of IDE
187: try {
188: fo.delete();
189: } catch (IOException e) {
190: // Output warning to IDE log and continue
191: IdeUtil.logWarning(e);
192: }
193: }
194: }
195: }
196:
197: public long getTimeStamp(ExtensionComplib extCompLib) {
198: // Current impl uses the modification time of the root dir of the
199: // expanded complib
200: return extCompLib.getDirectory().lastModified();
201: }
202:
203: private File ensureInstallHome() throws FileNotFoundException {
204: // Create installHome if it doesn't exist
205: if (!installHome.exists()) {
206: if (!installHome.mkdirs()) {
207: throw new FileNotFoundException(installHome
208: .getAbsolutePath());
209: }
210: }
211: return installHome;
212: }
213:
214: /**
215: * Install a complib package into this scope and return the newly installed complib.
216: *
217: * @param pkg
218: * @return
219: * @throws IOException
220: * @throws ComplibException
221: */
222: ExtensionComplib installComplibPackage(ComplibPackage pkg)
223: throws IOException, ComplibException {
224: // Find a unique absolute lib dir in this scope
225: File packageFile = pkg.getPackageFile();
226: String baseName = packageFile.getName();
227: String prefix = IdeUtil.removeWhiteSpace(IdeUtil
228: .removeExtension(baseName));
229: File dstDir = IdeUtil.findUniqueFile(ensureInstallHome(),
230: prefix, "");
231:
232: // Expand the src complib into this scope
233: IdeUtil.unzip(packageFile, dstDir);
234: return createComplib(dstDir);
235: }
236:
237: /**
238: * Install an existing complib into this scope and return the newly installed complib.
239: *
240: * @param srcComplib
241: * source complib
242: * @return
243: * @throws IOException
244: * @throws ComplibException
245: */
246: ExtensionComplib installComplib(ExtensionComplib srcComplib)
247: throws IOException, ComplibException {
248: // Find a unique absolute lib dir in this scope
249: String baseName = srcComplib.getDirectoryBaseName();
250: File dstDir = IdeUtil.findUniqueFile(ensureInstallHome(),
251: baseName, "");
252:
253: // Copy an already expanded complib into this scope
254: IdeUtil.copyFileRecursive(srcComplib.getDirectory(), dstDir);
255: return createComplib(dstDir);
256: }
257:
258: private ExtensionComplib createComplib(File dstDir)
259: throws ComplibException, IOException {
260: ExtensionComplib dstComplib = new ExtensionComplib(dstDir);
261: complibSet.add(dstComplib);
262: directorySet.add(dstComplib.getDirectoryBaseName());
263: persistDirectoryIndex();
264: return dstComplib;
265: }
266:
267: static void copyFile(File source, File dest) throws IOException {
268: File newItem = null;
269: if (dest.isDirectory()) {
270: newItem = new File(dest, source.getName());
271: } else {
272: newItem = dest;
273: }
274:
275: if (source.isDirectory()) {
276: newItem.mkdir();
277: File[] contents = source.listFiles();
278:
279: for (int i = 0; i < contents.length; i++) {
280: copyFile(contents[i], newItem);
281: }
282: } else {
283: BufferedInputStream in = null;
284: BufferedOutputStream out = null;
285:
286: try {
287: in = new BufferedInputStream(
288: new FileInputStream(source));
289: out = new BufferedOutputStream(new FileOutputStream(
290: newItem));
291: int c;
292: while ((c = in.read()) != -1)
293: out.write(c);
294:
295: } catch (IOException e) {
296: throw e;
297: } finally {
298: if (in != null)
299: in.close();
300: if (out != null)
301: out.close();
302: }
303: }
304: }
305:
306: /**
307: * Remove an installed component library
308: *
309: * @param library
310: * @param scope
311: */
312: public void remove(ExtensionComplib library) {
313: directorySet.remove(library.getDirectoryBaseName());
314: complibSet.remove(library);
315:
316: // Persist info on new component lib
317: try {
318: persistDirectoryIndex();
319: } catch (Exception e) {
320: // This failure mode will be handled up when IDE starts up again
321: }
322:
323: // Try to remove the library dir. If it fails, it will be cleaned up
324: // when IDE starts up.
325: File libDir = library.getDirectory();
326: // ProjectUtil.recursiveDelete(libDir);
327: recursiveDelete(libDir);
328: }
329:
330: // Exercise with caution!
331: private static boolean recursiveDelete(File f) {
332: // Basic safeguard - bail if we're asked to delete the root directory
333: if (f.getParentFile() == null)
334: return false;
335: if (f.isDirectory()) {
336: File[] contents = f.listFiles();
337: for (int i = 0; i < contents.length; i++) {
338: recursiveDelete(contents[i]);
339: }
340: }
341: return f.delete();
342: }
343:
344: /**
345: * Returns an existing complib in this scope with a matching complib Id or null.
346: *
347: * @param complib
348: * @return
349: */
350: public ExtensionComplib getExistingComplib(Complib complib) {
351: Complib.Identifier id = complib.getIdentifier();
352: for (ExtensionComplib iComplib : complibSet) {
353: if (iComplib.getIdentifier().equals(id)) {
354: return iComplib;
355: }
356: }
357: return null;
358: }
359:
360: /**
361: * Returns true iff scope contains a complib with the same Id
362: *
363: * @param complib
364: * @return
365: */
366: boolean contains(ExtensionComplib complib) {
367: return getExistingComplib(complib) != null;
368: }
369:
370: /**
371: * Return the time this Scope was last modified
372: *
373: * @return
374: */
375: public long getLastModified() {
376: return getIndexFile().lastModified();
377: }
378:
379: /**
380: * Return the component libraries in this scope
381: *
382: * @return
383: */
384: public Set<ExtensionComplib> getComplibs() {
385: return complibSet;
386: }
387:
388: public String toString() {
389: return "{installHome='" + installHome + "', directorySet=" // NOI18N
390: + directorySet + ", complibSet=" + complibSet // NOI18N
391: + "}"; // NOI18N
392: }
393:
394: /**
395: * @return The index.xml File object for this scope.
396: * @throws FileNotFoundException
397: * if scope dir is not found or cannot be created which should not normally occur
398: */
399: private File getIndexFile() {
400: return new File(installHome, LIBRARY_INDEX_FILENAME);
401: }
402:
403: /**
404: * Read directories from an index XML file
405: *
406: * @return Set of String-s, possibly empty. Never null.
407: */
408: private Set<String> readDirectoryIndex() {
409: File indexFile = getIndexFile();
410: if (!indexFile.exists()) {
411: // No index file so return previously initialized empty set
412: return directorySet;
413: }
414:
415: XmlUtil xmlIn = new XmlUtil();
416: Document doc;
417: try {
418: doc = xmlIn.read(indexFile);
419: } catch (XmlException e) {
420: // Assume list is empty
421: return directorySet;
422: }
423: Element docElement = doc.getDocumentElement();
424: NodeList nl = docElement
425: .getElementsByTagName(DIRECTORY_ELEMENT);
426: for (int i = 0; i < nl.getLength(); i++) {
427: Element dirElement = (Element) nl.item(i);
428: String dirName = dirElement
429: .getAttribute(DIRECTORY_NAME_ATTR);
430: directorySet.add(dirName);
431: }
432: return directorySet;
433: }
434:
435: /**
436: * Persist set of directories to this scope in an XML file
437: *
438: * @throws Exception
439: */
440: private void persistDirectoryIndex() throws IOException {
441: File indexFile = getIndexFile();
442: XmlUtil xmlOut = new XmlUtil();
443: Document doc = xmlOut.createDocument();
444: Comment docComment = doc
445: .createComment(" Directory list containing valid component libraries which is" // NOI18N
446: + " a workaround for Win32 file deletion problems."); // NOI18N
447: doc.appendChild(docComment);
448: Element docElement = doc.createElement(VALID_DIRECTORY_LIST);
449: doc.appendChild(docElement);
450:
451: for (String dirName : directorySet) {
452: Element dirElm = doc.createElement(DIRECTORY_ELEMENT);
453: dirElm.setAttribute(DIRECTORY_NAME_ATTR, dirName);
454: docElement.appendChild(dirElm);
455: }
456:
457: xmlOut.write(indexFile);
458: }
459: }
|