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.apisupport.project.layers;
043:
044: import java.beans.PropertyChangeEvent;
045: import java.beans.PropertyChangeListener;
046: import java.beans.PropertyChangeSupport;
047: import java.beans.PropertyVetoException;
048: import java.io.File;
049: import java.io.IOException;
050: import java.io.OutputStream;
051: import java.net.URL;
052: import java.util.ArrayList;
053: import java.util.Arrays;
054: import java.util.Collections;
055: import java.util.Enumeration;
056: import java.util.HashSet;
057: import java.util.LinkedList;
058: import java.util.List;
059: import java.util.Locale;
060: import java.util.Map;
061: import java.util.Set;
062: import java.util.WeakHashMap;
063: import java.util.jar.Manifest;
064: import java.util.regex.Matcher;
065: import java.util.regex.Pattern;
066: import org.netbeans.api.java.classpath.ClassPath;
067: import org.netbeans.api.project.Project;
068: import org.netbeans.api.project.ProjectManager;
069: import org.netbeans.modules.apisupport.project.EditableManifest;
070: import org.netbeans.modules.apisupport.project.ManifestManager;
071: import org.netbeans.modules.apisupport.project.NbModuleProject;
072: import org.netbeans.modules.apisupport.project.NbModuleProjectGenerator;
073: import org.netbeans.modules.apisupport.project.Util;
074: import org.netbeans.modules.apisupport.project.spi.NbModuleProvider;
075: import org.netbeans.modules.apisupport.project.suite.SuiteProject;
076: import org.netbeans.modules.apisupport.project.ui.customizer.SuiteProperties;
077: import org.netbeans.modules.apisupport.project.ui.customizer.SuiteUtils;
078: import org.netbeans.modules.apisupport.project.universe.ModuleEntry;
079: import org.netbeans.modules.apisupport.project.universe.ModuleList;
080: import org.netbeans.modules.apisupport.project.universe.NbPlatform;
081: import org.netbeans.modules.xml.tax.cookies.TreeEditorCookie;
082: import org.netbeans.modules.xml.tax.parser.XMLParsingSupport;
083: import org.netbeans.spi.java.classpath.support.ClassPathSupport;
084: import org.netbeans.spi.project.support.ant.PropertyEvaluator;
085: import org.netbeans.tax.TreeDocumentRoot;
086: import org.netbeans.tax.TreeException;
087: import org.netbeans.tax.TreeObject;
088: import org.netbeans.tax.io.TreeStreamResult;
089: import org.openide.ErrorManager;
090: import org.openide.filesystems.FileAttributeEvent;
091: import org.openide.filesystems.FileChangeListener;
092: import org.openide.filesystems.FileEvent;
093: import org.openide.filesystems.FileLock;
094: import org.openide.filesystems.FileObject;
095: import org.openide.filesystems.FileRenameEvent;
096: import org.openide.filesystems.FileStateInvalidException;
097: import org.openide.filesystems.FileSystem;
098: import org.openide.filesystems.FileUtil;
099: import org.openide.filesystems.MultiFileSystem;
100: import org.openide.filesystems.XMLFileSystem;
101: import org.openide.util.Task;
102: import org.xml.sax.InputSource;
103: import org.xml.sax.SAXException;
104:
105: /**
106: * Misc support for dealing with layers.
107: * @author Jesse Glick
108: */
109: public class LayerUtils {
110:
111: private LayerUtils() {
112: }
113:
114: /**
115: * Translates nbres: into nbrescurr: for internal use.
116: * Returns an array of one or more URLs.
117: * May just be original URL, but will try to produce URLs corresponding to real files.
118: * If there is a suffix, may produce several, most specific first.
119: */
120: static URL[] currentify(URL u, String suffix, ClassPath cp) {
121: if (cp == null) {
122: return new URL[] { u };
123: }
124: try {
125: if (u.getProtocol().equals("nbres")) { // NOI18N
126: String path = u.getFile();
127: if (path.startsWith("/"))
128: path = path.substring(1); // NOI18N
129: FileObject fo = cp.findResource(path);
130: if (fo != null) {
131: return new URL[] { fo.getURL() };
132: }
133: } else if (u.getProtocol().equals("nbresloc")) { // NOI18N
134: List<URL> urls = new ArrayList<URL>();
135: String path = u.getFile();
136: if (path.startsWith("/"))
137: path = path.substring(1); // NOI18N
138: int idx = path.lastIndexOf('/');
139: String folder;
140: String nameext;
141: if (idx == -1) {
142: folder = ""; // NOI18N
143: nameext = path;
144: } else {
145: folder = path.substring(0, idx + 1);
146: nameext = path.substring(idx + 1);
147: }
148: idx = nameext.lastIndexOf('.');
149: String name;
150: String ext;
151: if (idx == -1) {
152: name = nameext;
153: ext = ""; // NOI18N
154: } else {
155: name = nameext.substring(0, idx);
156: ext = nameext.substring(idx);
157: }
158: List<String> suffixes = new ArrayList<String>(
159: computeSubVariants(suffix));
160: suffixes.add(suffix);
161: Collections.reverse(suffixes);
162: for (String trysuffix : suffixes) {
163: String trypath = folder + name + trysuffix + ext;
164: FileObject fo = cp.findResource(trypath);
165: if (fo != null) {
166: urls.add(fo.getURL());
167: }
168: }
169: if (!urls.isEmpty()) {
170: return urls.toArray(new URL[urls.size()]);
171: }
172: }
173: } catch (FileStateInvalidException fsie) {
174: Util.err.notify(ErrorManager.WARNING, fsie);
175: }
176: return new URL[] { u };
177: }
178:
179: // E.g. for name 'foo_f4j_ce_ja', should produce list:
180: // 'foo', 'foo_ja', 'foo_f4j', 'foo_f4j_ja', 'foo_f4j_ce'
181: // Will actually produce:
182: // 'foo', 'foo_ja', 'foo_ce', 'foo_ce_ja', 'foo_f4j', 'foo_f4j_ja', 'foo_f4j_ce'
183: // since impossible to distinguish locale from branding reliably.
184: private static List<String> computeSubVariants(String name) {
185: int idx = name.indexOf('_');
186: if (idx == -1) {
187: return Collections.emptyList();
188: } else {
189: String base = name.substring(0, idx);
190: String suffix = name.substring(idx);
191: List<String> l = computeSubVariants(base, suffix);
192: return l.subList(0, l.size() - 1);
193: }
194: }
195:
196: private static List<String> computeSubVariants(String base,
197: String suffix) {
198: int idx = suffix.indexOf('_', 1);
199: if (idx == -1) {
200: List<String> l = new LinkedList<String>();
201: l.add(base);
202: l.add(base + suffix);
203: return l;
204: } else {
205: String remainder = suffix.substring(idx);
206: List<String> l1 = computeSubVariants(base, remainder);
207: List<String> l2 = computeSubVariants(base
208: + suffix.substring(0, idx), remainder);
209: List<String> l = new LinkedList<String>(l1);
210: l.addAll(l2);
211: return l;
212: }
213: }
214:
215: // XXX needs to hold a strong ref only when modified, probably?
216: private static final Map<Project, LayerHandle> layerHandleCache = new WeakHashMap<Project, LayerHandle>();
217:
218: /**
219: * Gets a handle for one project's XML layer.
220: */
221: public static LayerHandle layerForProject(Project project) {
222: LayerHandle handle = layerHandleCache.get(project);
223: if (handle == null) {
224: handle = new LayerHandle(project);
225: layerHandleCache.put(project, handle);
226: }
227: return handle;
228: }
229:
230: private static final Set<String> XML_LIKE_TYPES = new HashSet<String>();
231: static {
232: XML_LIKE_TYPES.add(".settings"); // NOI18N
233: XML_LIKE_TYPES.add(".wstcref"); // NOI18N
234: XML_LIKE_TYPES.add(".wsmode"); // NOI18N
235: XML_LIKE_TYPES.add(".wsgrp"); // NOI18N
236: XML_LIKE_TYPES.add(".wsmgr"); // NOI18N
237: }
238:
239: /**
240: * Find the name of the external file that will be generated for a given
241: * layer path if it is created with contents.
242: * @param parent parent folder, or null
243: * @param layerPath full path in layer
244: * @return a simple file name
245: */
246: public static String findGeneratedName(FileObject parent,
247: String layerPath) {
248: Matcher m = Pattern.compile("(.+/)?([^/.]+)(\\.[^/]+)?")
249: .matcher(layerPath); // NOI18N
250: if (!m.matches()) {
251: throw new IllegalArgumentException(layerPath);
252: }
253: String base = m.group(2);
254: String ext = m.group(3);
255: if (ext == null) {
256: ext = "";
257: } else if (ext.equals(".java")) { // NOI18N
258: ext = "_java"; // NOI18N
259: } else if (XML_LIKE_TYPES.contains(ext)) {
260: String upper = ext.substring(1, 2).toUpperCase(
261: Locale.ENGLISH);
262: base = base + upper + ext.substring(2);
263: ext = ".xml"; // NOI18N
264: }
265: String name = base + ext;
266: if (parent == null || parent.getFileObject(name) == null) {
267: return name;
268: } else {
269: for (int i = 1; true; i++) {
270: name = base + '_' + i + ext;
271: if (parent.getFileObject(name) == null) {
272: return name;
273: }
274: }
275: }
276: }
277:
278: /**
279: * Representation of in-memory TAX tree which can be saved upon request.
280: */
281: interface SavableTreeEditorCookie extends TreeEditorCookie {
282:
283: /** property change fired when dirty flag changes */
284: String PROP_DIRTY = "dirty"; // NOI18N
285:
286: /** true if there are in-memory mods */
287: boolean isDirty();
288:
289: /** try to save any in-memory mods to disk */
290: void save() throws IOException;
291:
292: }
293:
294: private static final class CookieImpl implements
295: SavableTreeEditorCookie, FileChangeListener {
296: private TreeDocumentRoot root;
297: private boolean dirty;
298: private Exception problem;
299: private final FileObject f;
300: private final PropertyChangeSupport pcs = new PropertyChangeSupport(
301: this );
302: private boolean saving;
303:
304: public CookieImpl(FileObject f) {
305: //System.err.println("new CookieImpl for " + f);
306: this .f = f;
307: f.addFileChangeListener(FileUtil.weakFileChangeListener(
308: this , f));
309: }
310:
311: public TreeDocumentRoot getDocumentRoot() {
312: return root;
313: }
314:
315: public int getStatus() {
316: if (problem != null) {
317: return TreeEditorCookie.STATUS_ERROR;
318: } else if (root != null) {
319: return TreeEditorCookie.STATUS_OK;
320: } else {
321: return TreeEditorCookie.STATUS_NOT;
322: }
323: }
324:
325: public TreeDocumentRoot openDocumentRoot() throws IOException,
326: TreeException {
327: if (root == null && f.isValid()) {
328: try {
329: //System.err.println("openDocumentRoot: really opening");
330: boolean oldDirty = dirty;
331: int oldStatus = getStatus();
332: root = new XMLParsingSupport()
333: .parse(new InputSource(f.getURL()
334: .toExternalForm()));
335: problem = null;
336: dirty = false;
337: pcs.firePropertyChange(PROP_DIRTY, oldDirty, false);
338: pcs.firePropertyChange(PROP_STATUS, oldStatus,
339: TreeEditorCookie.STATUS_OK);
340: //pcs.firePropertyChange(PROP_DOCUMENT_ROOT, null, root);
341: } catch (IOException e) {
342: problem = e;
343: throw e;
344: } catch (TreeException e) {
345: problem = e;
346: throw e;
347: }
348: ((TreeObject) root)
349: .addPropertyChangeListener(new PropertyChangeListener() {
350: public void propertyChange(
351: PropertyChangeEvent evt) {
352: //System.err.println("tree modified");
353: modified();
354: }
355: });
356: }
357: return root;
358: }
359:
360: public Task prepareDocumentRoot() {
361: throw new UnsupportedOperationException();
362: }
363:
364: public void addPropertyChangeListener(
365: PropertyChangeListener listener) {
366: pcs.addPropertyChangeListener(listener);
367: }
368:
369: public void removePropertyChangeListener(
370: PropertyChangeListener listener) {
371: pcs.removePropertyChangeListener(listener);
372: }
373:
374: private void modified() {
375: //System.err.println("modified(): dirty=" + dirty + " in " + Thread.currentThread().getName() + " for " + this);
376: if (!dirty) {
377: dirty = true;
378: pcs.firePropertyChange(PROP_DIRTY, false, true);
379: }
380: }
381:
382: public boolean isDirty() {
383: return dirty;
384: }
385:
386: public synchronized void save() throws IOException {
387: //System.err.println("save(): dirty=" + dirty + " in " + Thread.currentThread().getName() + " for " + this);
388: if (root == null || !dirty) {
389: return;
390: }
391: assert !saving;
392: saving = true;
393: //System.err.println("saving");
394: try {
395: FileLock lock = f.lock();
396: try {
397: OutputStream os = f.getOutputStream(lock);
398: try {
399: new TreeStreamResult(os).getWriter(root)
400: .writeDocument();
401: } catch (TreeException e) {
402: throw (IOException) new IOException(e
403: .toString()).initCause(e);
404: } finally {
405: os.close();
406: }
407: } finally {
408: lock.releaseLock();
409: }
410: } finally {
411: saving = false;
412: //System.err.println("!saving in " + Thread.currentThread().getName() + " for " + this);
413: }
414: dirty = false;
415: pcs.firePropertyChange(PROP_DIRTY, true, false);
416: }
417:
418: public void fileChanged(FileEvent fe) {
419: changed();
420: }
421:
422: public void fileDeleted(FileEvent fe) {
423: changed();
424: }
425:
426: public void fileRenamed(FileRenameEvent fe) {
427: changed();
428: }
429:
430: public void fileAttributeChanged(FileAttributeEvent fe) {
431: // ignore
432: }
433:
434: public void fileFolderCreated(FileEvent fe) {
435: assert false;
436: }
437:
438: public void fileDataCreated(FileEvent fe) {
439: assert false;
440: }
441:
442: private void changed() {
443: //System.err.println("changed on disk; saving=" + saving + " in " + Thread.currentThread().getName() + " for " + this);
444: synchronized (this ) {
445: if (saving) {
446: return;
447: }
448: problem = null;
449: dirty = false;
450: root = null;
451: }
452: pcs.firePropertyChange(PROP_DOCUMENT_ROOT, null, null);
453: }
454: }
455:
456: static SavableTreeEditorCookie cookieForFile(FileObject f) {
457: return new CookieImpl(f);
458: }
459:
460: /**
461: * Manages one project's XML layer.
462: */
463: public static final class LayerHandle {
464:
465: private final Project project;
466: private FileSystem fs;
467: private SavableTreeEditorCookie cookie;
468: private boolean autosave;
469:
470: LayerHandle(Project project) {
471: //System.err.println("new LayerHandle for " + project);
472: this .project = project;
473: }
474:
475: /**
476: * Get the layer as a structured filesystem.
477: * You can make whatever Filesystems API calls you like to it.
478: * Just call {@link #save} when you are done so the modified XML document is saved
479: * (or the user can save it explicitly if you don't).
480: * @param create if true, and there is no layer yet, create it now; if false, just return null
481: */
482: public synchronized FileSystem layer(boolean create) {
483: if (fs == null) {
484: FileObject xml = getLayerFile();
485: if (xml == null) {
486: if (!create) {
487: return null;
488: }
489: try {
490: NbModuleProvider module = project.getLookup()
491: .lookup(NbModuleProvider.class);
492: FileObject manifest = module.getManifestFile();
493: if (manifest != null) { // #121056
494: // Check to see if the manifest entry is already specified.
495: String layerSrcPath = ManifestManager
496: .getInstance(
497: Util.getManifest(manifest),
498: false).getLayer();
499: if (layerSrcPath == null) {
500: layerSrcPath = newLayerPath();
501: EditableManifest m = Util
502: .loadManifest(manifest);
503: m
504: .setAttribute(
505: ManifestManager.OPENIDE_MODULE_LAYER,
506: layerSrcPath, null);
507: Util.storeManifest(manifest, m);
508: }
509: }
510: xml = NbModuleProjectGenerator.createLayer(
511: project.getProjectDirectory(),
512: module.getResourceDirectoryPath(false)
513: + '/' + newLayerPath());
514: } catch (IOException e) {
515: Util.err.notify(ErrorManager.INFORMATIONAL, e);
516: return fs = FileUtil.createMemoryFileSystem();
517: }
518: }
519: try {
520: fs = new WritableXMLFileSystem(xml.getURL(),
521: cookie = cookieForFile(xml), /*XXX*/null);
522: } catch (FileStateInvalidException e) {
523: throw new AssertionError(e);
524: }
525: cookie
526: .addPropertyChangeListener(new PropertyChangeListener() {
527: public void propertyChange(
528: PropertyChangeEvent evt) {
529: //System.err.println("changed in mem");
530: if (autosave
531: && SavableTreeEditorCookie.PROP_DIRTY
532: .equals(evt
533: .getPropertyName())) {
534: //System.err.println(" will save...");
535: try {
536: save();
537: } catch (IOException e) {
538: Util.err
539: .notify(
540: ErrorManager.INFORMATIONAL,
541: e);
542: }
543: }
544: }
545: });
546: }
547: return fs;
548: }
549:
550: /**
551: * Save the layer, if it was in fact modified.
552: * Note that nonempty layer entries you created will already be on disk.
553: */
554: public void save() throws IOException {
555: if (cookie == null) {
556: throw new IOException("Cannot save a nonexistent layer"); // NOI18N
557: }
558: cookie.save();
559: }
560:
561: /**
562: * Find the XML layer file for this project, if it exists.
563: * @return the layer, or null
564: */
565: public FileObject getLayerFile() {
566: NbModuleProvider module = project.getLookup().lookup(
567: NbModuleProvider.class);
568: if (module == null) { // #126939: other project type
569: return null;
570: }
571: Manifest mf = Util.getManifest(module.getManifestFile());
572: if (mf == null) {
573: return null;
574: }
575: String path = ManifestManager.getInstance(mf, false)
576: .getLayer();
577: if (path == null) {
578: return null;
579: }
580: return Util.getResourceDirectory(project).getFileObject(
581: path);
582: }
583:
584: /**
585: * Set whether to automatically save changes to disk.
586: * @param true to save changes immediately, false to save only upon request
587: */
588: public void setAutosave(boolean autosave) {
589: this .autosave = autosave;
590: if (autosave && cookie != null) {
591: try {
592: cookie.save();
593: } catch (IOException e) {
594: Util.err.notify(ErrorManager.INFORMATIONAL, e);
595: }
596: }
597: }
598:
599: /**
600: * Check whether this handle is currently in autosave mode.
601: */
602: public boolean isAutosave() {
603: return autosave;
604: }
605:
606: /**
607: * Resource path in which to make a new XML layer.
608: */
609: private String newLayerPath() {
610: NbModuleProvider module = project.getLookup().lookup(
611: NbModuleProvider.class);
612: FileObject manifest = module.getManifestFile();
613: if (manifest != null) {
614: String bundlePath = ManifestManager.getInstance(
615: Util.getManifest(manifest), false)
616: .getLocalizingBundle();
617: if (bundlePath != null) {
618: return bundlePath.replaceFirst("/[^/]+$",
619: "/layer.xml"); // NOI18N
620: }
621: }
622: return module.getCodeNameBase().replace('.', '/')
623: + "/layer.xml"; // NOI18N
624: }
625:
626: public @Override
627: String toString() {
628: FileObject layer = getLayerFile();
629: if (layer != null) {
630: return FileUtil.getFileDisplayName(layer);
631: } else {
632: return FileUtil.getFileDisplayName(project
633: .getProjectDirectory());
634: }
635: }
636:
637: }
638:
639: /**
640: * Get a filesystem that will look like what this project would "see".
641: * <p>There are four possibilities:</p>
642: * <ol>
643: * <li><p>For a standalone module project, the filesystem will include all the XML
644: * layers from all modules in the selected platform, plus this module's XML layer
645: * as the writable layer (use {@link LayerHandle#save} to save changes as needed).</p></li>
646: * <li><p>For a module suite project, the filesystem will include all the XML layers
647: * from all modules in the selected platform which are not excluded in the current
648: * suite configuration, plus the XML layers for modules in the suite (currently all
649: * read-only, i.e. the filesystem is read-only).</p></li>
650: * <li><p>For a suite component module project, the filesystem will include all XML
651: * layers from non-excluded platform modules, plus the XML layers for modules in the
652: * suite, with this module's layer being writable.</p></li>
653: * <li><p>For a netbeans.org module, the filesystem will include all XML layers
654: * from all netbeans.org modules that are not in the <code>extra</code> cluster,
655: * plus the layer from this module (if it is in the <code>extra</code> cluster,
656: * with this module's layer always writable.</p></li>
657: * </ol>
658: * <p>Does not currently attempt to cache the result,
659: * though that could be attempted later as needed.</p>
660: * <p>Will try to produce pleasant-looking display names and/or icons for files.</p>
661: * <p>Note that parsing XML layers is not terribly fast so it would be wise to show
662: * a "please wait" label or some other simple progress indication while this
663: * is being called, if blocking the UI.</p>
664: * @param project a project of one of the three types enumerated above
665: * @return the effective system filesystem seen by that project
666: * @throws IOException if there were problems loading layers, etc.
667: * @see "#62257"
668: */
669: public static FileSystem getEffectiveSystemFilesystem(Project p)
670: throws IOException {
671:
672: NbModuleProvider.NbModuleType type = Util.getModuleType(p);
673: FileSystem projectLayer = layerForProject(p).layer(false);
674: if (type == NbModuleProvider.STANDALONE) {
675: Set<File> jars = getPlatformJarsForStandaloneProject(p);
676: FileSystem[] platformLayers = getPlatformLayers(jars);
677: ClassPath cp = createLayerClasspath(Collections
678: .singleton(p), jars);
679: return mergeFilesystems(projectLayer, platformLayers, cp);
680: } else if (type == NbModuleProvider.SUITE_COMPONENT) {
681: SuiteProject suite = SuiteUtils.findSuite(p);
682: if (suite == null) {
683: throw new IOException("Could not load suite for " + p); // NOI18N
684: }
685: List<FileSystem> readOnlyLayers = new ArrayList<FileSystem>();
686: Set<NbModuleProject> modules = SuiteUtils
687: .getSubProjects(suite);
688: for (NbModuleProject sister : modules) {
689: if (sister == p) {
690: continue;
691: }
692: LayerHandle handle = layerForProject(sister);
693: FileSystem roLayer = handle.layer(false);
694: if (roLayer != null) {
695: readOnlyLayers.add(roLayer);
696: }
697: }
698: Set<File> jars = getPlatformJarsForSuiteComponentProject(p,
699: suite);
700: readOnlyLayers.addAll(Arrays
701: .asList(getPlatformLayers(jars)));
702: ClassPath cp = createLayerClasspath(modules, jars);
703: return mergeFilesystems(projectLayer, readOnlyLayers
704: .toArray(new FileSystem[readOnlyLayers.size()]), cp);
705: } else if (type == NbModuleProvider.NETBEANS_ORG) {
706: //it's safe to cast to NbModuleProject here.
707: NbModuleProject nbprj = p.getLookup().lookup(
708: NbModuleProject.class);
709: Set<NbModuleProject> projects = getProjectsForNetBeansOrgProject(nbprj);
710: List<URL> otherLayerURLs = new ArrayList<URL>();
711:
712: for (NbModuleProject p2 : projects) {
713: if (p2.getManifest() == null) {
714: //profiler for example.
715: continue;
716: }
717: ManifestManager mm = ManifestManager.getInstance(p2
718: .getManifest(), false);
719: String layer = mm.getLayer();
720: if (layer == null) {
721: continue;
722: }
723: FileObject src = p2.getSourceDirectory();
724: if (src == null) {
725: continue;
726: }
727: FileObject layerXml = src.getFileObject(layer);
728: if (layerXml == null) {
729: continue;
730: }
731: otherLayerURLs.add(layerXml.getURL());
732: }
733: XMLFileSystem xfs = new XMLFileSystem();
734: try {
735: xfs.setXmlUrls(otherLayerURLs
736: .toArray(new URL[otherLayerURLs.size()]));
737: } catch (PropertyVetoException ex) {
738: assert false : ex;
739: }
740: ClassPath cp = createLayerClasspath(projects, Collections
741: .<File> emptySet());
742: return mergeFilesystems(projectLayer,
743: new FileSystem[] { xfs }, cp);
744: } else {
745: throw new AssertionError(type);
746: }
747: }
748:
749: /**
750: * Get the platform JARs associated with a standalone module project.
751: */
752: public static Set<File> getPlatformJarsForStandaloneProject(
753: Project project) {
754: NbModuleProvider mod = project.getLookup().lookup(
755: NbModuleProvider.class);
756: //TODO create a utility method to do this if needed in more places
757: NbPlatform platform = null;
758: File platformDir = mod.getActivePlatformLocation();
759: if (platformDir != null) {
760: platform = NbPlatform.getPlatformByDestDir(platformDir);
761: }
762: if (platform == null || !platform.isValid()) {
763: platform = NbPlatform.getDefaultPlatform();
764: }
765: return getPlatformJars(platform, null, null, null);
766: }
767:
768: public static Set<File> getPlatformJarsForSuiteComponentProject(
769: Project project, SuiteProject suite) {
770: NbPlatform platform = suite.getPlatform(true);
771: PropertyEvaluator eval = suite.getEvaluator();
772: String[] includedClusters = SuiteProperties.getArrayProperty(
773: eval, SuiteProperties.ENABLED_CLUSTERS_PROPERTY);
774: String[] excludedClusters = SuiteProperties.getArrayProperty(
775: eval, SuiteProperties.DISABLED_CLUSTERS_PROPERTY);
776: String[] excludedModules = SuiteProperties.getArrayProperty(
777: eval, SuiteProperties.DISABLED_MODULES_PROPERTY);
778: return getPlatformJars(platform, includedClusters,
779: excludedClusters, excludedModules);
780: }
781:
782: public static Set<NbModuleProject> getProjectsForNetBeansOrgProject(
783: NbModuleProject project) throws IOException {
784: ModuleList list = project.getModuleList();
785: Set<NbModuleProject> projects = new HashSet<NbModuleProject>();
786: projects.add(project);
787: for (ModuleEntry other : list.getAllEntriesSoft()) {
788: if (other.getClusterDirectory().getName().equals("extra")) { // NOI18N
789: continue;
790: }
791: File root = other.getSourceLocation();
792: assert root != null : other;
793: NbModuleProject p2 = (NbModuleProject) ProjectManager
794: .getDefault().findProject(
795: FileUtil.toFileObject(root));
796: if (p2 == null) {
797: continue;
798: }
799: projects.add(p2);
800: }
801: return projects;
802: }
803:
804: /**
805: * Finds all the module JARs in the platform.
806: * Can optionally pass non-null lists of cluster names and module CNBs to exclude, as per suite properties.
807: */
808: private static Set<File> getPlatformJars(NbPlatform platform,
809: String[] includedClusters, String[] excludedClusters,
810: String[] excludedModules) {
811: if (platform == null) {
812: return Collections.emptySet();
813: }
814: Set<String> includedClustersS = (includedClusters != null) ? new HashSet<String>(
815: Arrays.asList(includedClusters))
816: : Collections.<String> emptySet();
817: Set<String> excludedClustersS = (excludedClusters != null) ? new HashSet<String>(
818: Arrays.asList(excludedClusters))
819: : Collections.<String> emptySet();
820: Set<String> excludedModulesS = (excludedModules != null) ? new HashSet<String>(
821: Arrays.asList(excludedModules))
822: : Collections.<String> emptySet();
823: ModuleEntry[] entries = platform.getModules();
824: Set<File> jars = new HashSet<File>(entries.length);
825: for (ModuleEntry entry : entries) {
826: if (!includedClustersS.isEmpty()
827: && !includedClustersS.contains(entry
828: .getClusterDirectory().getName())) {
829: continue;
830: }
831: if (includedClustersS.isEmpty()
832: && excludedClustersS.contains(entry
833: .getClusterDirectory().getName())) {
834: continue;
835: }
836: if (excludedModulesS.contains(entry.getCodeNameBase())) {
837: continue;
838: }
839: jars.add(entry.getJarLocation());
840: }
841: return jars;
842: }
843:
844: /**
845: * Constructs a list of filesystems representing the XML layers of the supplied platform module JARs.
846: */
847: private static FileSystem[] getPlatformLayers(Set<File> platformJars)
848: throws IOException {
849: List<FileSystem> layers = new ArrayList<FileSystem>();
850: JAR: for (File jar : platformJars) {
851: ManifestManager mm = ManifestManager
852: .getInstanceFromJAR(jar);
853: for (String tok : mm.getRequiredTokens()) {
854: if (tok.startsWith("org.openide.modules.os.")) { // NOI18N
855: // Best to exclude platform-specific modules, e.g. ide/applemenu, as they can cause confusion.
856: continue JAR;
857: }
858: }
859: String layer = mm.getLayer();
860: if (layer != null) {
861: URL u = new URL("jar:" + jar.toURI() + "!/" + layer);
862: try {
863: // XXX nbres: and such URL protocols may not work in platform layers
864: // (cf. org.openide.filesystems.ExternalUtil.findClass)
865: FileSystem xfs = new XMLFileSystem(u);
866: boolean hasMasks = false;
867: Enumeration e = xfs.getRoot().getChildren(true);
868: while (e.hasMoreElements()) {
869: FileObject f = (FileObject) e.nextElement();
870: if (f.getNameExt().endsWith("_hidden")) { // NOI18N
871: // #63295: put it at the beginning. Not as good as following module deps but probably close enough.
872: hasMasks = true;
873: break;
874: }
875: }
876: if (hasMasks) {
877: layers.add(0, xfs);
878: } else {
879: layers.add(xfs);
880: }
881: } catch (SAXException e) {
882: throw (IOException) new IOException(e.toString())
883: .initCause(e);
884: }
885: }
886: }
887: return layers.toArray(new FileSystem[layers.size()]);
888: }
889:
890: /**
891: * Creates a classpath representing the source roots and platform binary JARs for a project/suite.
892: */
893: static ClassPath createLayerClasspath(
894: Set<? extends Project> moduleProjects,
895: Set<File> platformJars) throws IOException {
896: List<URL> roots = new ArrayList<URL>();
897: for (Project p : moduleProjects) {
898: NbModuleProvider mod = p.getLookup().lookup(
899: NbModuleProvider.class);
900: FileObject src = mod.getSourceDirectory();
901: if (src != null) {
902: roots.add(src.getURL());
903: }
904: }
905: for (File jar : platformJars) {
906: roots.add(FileUtil.getArchiveRoot(jar.toURI().toURL()));
907: File locale = new File(jar.getParentFile(), "locale"); // NOI18N
908: if (locale.isDirectory()) {
909: String n = jar.getName();
910: int x = n.lastIndexOf('.');
911: if (x == -1) {
912: x = n.length();
913: }
914: String base = n.substring(0, x);
915: String ext = n.substring(x);
916: String[] variants = locale.list();
917: if (variants != null) {
918: for (int i = 0; i < variants.length; i++) {
919: if (variants[i].startsWith(base)
920: && variants[i].endsWith(ext)
921: && variants[i].charAt(x) == '_') {
922: roots.add(FileUtil.getArchiveRoot(new File(
923: locale, variants[i]).toURI()
924: .toURL()));
925: }
926: }
927: }
928: }
929: }
930: // XXX in principle, could add CP extensions from modules... but probably not necessary
931: return ClassPathSupport.createClassPath(roots
932: .toArray(new URL[roots.size()]));
933: }
934:
935: /**
936: * Create a merged filesystem from one writable layer (may be null) and some read-only layers.
937: * You should also pass a classpath that can be used to look up resource bundles and icons.
938: */
939: private static FileSystem mergeFilesystems(
940: FileSystem writableLayer, FileSystem[] readOnlyLayers,
941: final ClassPath cp) {
942: if (writableLayer == null) {
943: writableLayer = new XMLFileSystem();
944: }
945: final FileSystem[] layers = new FileSystem[readOnlyLayers.length + 1];
946: layers[0] = writableLayer;
947: System.arraycopy(readOnlyLayers, 0, layers, 1,
948: readOnlyLayers.length);
949: class BadgingMergedFileSystem extends MultiFileSystem {
950: private final BadgingSupport status;
951:
952: public BadgingMergedFileSystem() {
953: super (layers);
954: status = new BadgingSupport(this );
955: status.setClasspath(cp);
956: status.setSuffix("_" + Locale.getDefault());
957: // XXX listening?
958: }
959:
960: @Override
961: public FileSystem.Status getStatus() {
962: return status;
963: }
964: }
965: return new BadgingMergedFileSystem();
966: }
967:
968: }
|