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.queries;
043:
044: import java.beans.PropertyChangeEvent;
045: import java.beans.PropertyChangeListener;
046: import java.io.File;
047: import java.io.IOException;
048: import java.io.InputStream;
049: import java.net.MalformedURLException;
050: import java.net.URI;
051: import java.net.URL;
052: import java.util.ArrayList;
053: import java.util.Enumeration;
054: import java.util.HashMap;
055: import java.util.List;
056: import java.util.Map;
057: import java.util.StringTokenizer;
058: import java.util.zip.ZipEntry;
059: import java.util.zip.ZipException;
060: import java.util.zip.ZipFile;
061: import javax.swing.event.ChangeListener;
062: import org.netbeans.api.java.queries.SourceForBinaryQuery;
063: import org.netbeans.modules.apisupport.project.NbModuleProjectType;
064: import org.netbeans.modules.apisupport.project.Util;
065: import org.netbeans.modules.apisupport.project.universe.ModuleList;
066: import org.netbeans.modules.apisupport.project.universe.NbPlatform;
067: import org.netbeans.modules.apisupport.project.universe.TestEntry;
068: import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation;
069: import org.openide.ErrorManager;
070: import org.openide.filesystems.FileObject;
071: import org.openide.filesystems.FileUtil;
072: import org.openide.filesystems.URLMapper;
073: import org.openide.util.ChangeSupport;
074: import org.openide.xml.XMLUtil;
075: import org.w3c.dom.Document;
076: import org.w3c.dom.Element;
077: import org.xml.sax.InputSource;
078: import org.xml.sax.SAXException;
079:
080: /**
081: * Able to find sources in the NetBeans sources zip.
082: *
083: * @author Martin Krauskopf
084: */
085: public final class GlobalSourceForBinaryImpl implements
086: SourceForBinaryQueryImplementation {
087:
088: /** for use from unit tests */
089: static boolean quiet = false;
090:
091: public SourceForBinaryQuery.Result findSourceRoots(URL binaryRoot) {
092: try {
093: { // #68685 hack - associate reasonable sources with XTest's versions of various test libs
094: String binaryRootS = binaryRoot.toExternalForm();
095: URL result = null;
096: if (binaryRootS.startsWith("jar:file:")) { // NOI18N
097: if (binaryRootS
098: .endsWith("/xtest/lib/nbjunit.jar!/")) { // NOI18N
099: result = new URL(binaryRootS.substring("jar:"
100: .length(), binaryRootS.length()
101: - "/xtest/lib/nbjunit.jar!/".length())
102: + "/xtest/nbjunit/src/"); // NOI18N
103: } else if (binaryRootS
104: .endsWith("/xtest/lib/nbjunit-ide.jar!/")) { // NOI18N
105: result = new URL(binaryRootS.substring("jar:"
106: .length(), binaryRootS.length()
107: - "/xtest/lib/nbjunit-ide.jar!/"
108: .length())
109: + "/xtest/nbjunit/ide/src/"); // NOI18N
110: } else if (binaryRootS
111: .endsWith("/xtest/lib/insanelib.jar!/")) { // NOI18N
112: result = new URL(binaryRootS
113: .substring("jar:".length(), binaryRootS
114: .length()
115: - "/xtest/lib/insanelib.jar!/"
116: .length())
117: + "/performance/insanelib/src/"); // NOI18N
118: } else {
119: // tests.jar in test distribution
120: TestEntry testJar = TestEntry
121: .get(archiveURLToFile(binaryRoot));
122: if (testJar != null) {
123: result = testJar.getSrcDir();
124: }
125: }
126: final FileObject resultFO = result != null ? URLMapper
127: .findFileObject(result)
128: : null;
129: if (resultFO != null) {
130: return new SourceForBinaryQuery.Result() {
131: public FileObject[] getRoots() {
132: return new FileObject[] { resultFO };
133: }
134:
135: public void addChangeListener(
136: ChangeListener l) {
137: }
138:
139: public void removeChangeListener(
140: ChangeListener l) {
141: }
142: };
143: }
144: }
145: }
146: NbPlatform supposedPlaf = null;
147: for (NbPlatform plaf : NbPlatform.getPlatforms()) {
148: // XXX more robust condition?
149: if (binaryRoot.toExternalForm().indexOf(
150: plaf.getDestDir().toURI().toURL()
151: .toExternalForm()) != -1) {
152: supposedPlaf = plaf;
153: break;
154: }
155: }
156: if (supposedPlaf == null) {
157: return null;
158: }
159: if (!binaryRoot.getProtocol().equals("jar")) { // NOI18N
160: Util.err.log(binaryRoot + " is not an archive file."); // NOI18N
161: return null;
162: }
163: File binaryRootF = archiveURLToFile(binaryRoot);
164: FileObject fo = FileUtil.toFileObject(binaryRootF);
165: if (fo == null) {
166: Util.err.log("Cannot found FileObject for "
167: + binaryRootF + "(" + binaryRoot + ")"); // NOI18N
168: return null;
169: }
170: // if (testCnb != null && supposedPlaf != null) {
171: // test
172: // supposedPlaf.
173: // }
174: return new NbPlatformResult(supposedPlaf, binaryRoot, fo
175: .getName().replace('-', '.'));
176: } catch (IOException ex) {
177: throw new AssertionError(ex);
178: }
179: }
180:
181: private static final class NbPlatformResult implements
182: SourceForBinaryQuery.Result, PropertyChangeListener {
183:
184: private final ChangeSupport changeSupport = new ChangeSupport(
185: this );
186: private final NbPlatform platform;
187: private final URL binaryRoot;
188: private final String cnb;
189:
190: private boolean alreadyListening;
191:
192: NbPlatformResult(final NbPlatform platform,
193: final URL binaryRoot, final String cnb) {
194: this .platform = platform;
195: this .binaryRoot = binaryRoot;
196: this .cnb = cnb;
197: // this.testType = testType;
198: // this.testCluster = testCluster;
199: }
200:
201: public FileObject[] getRoots() {
202: final List<FileObject> candidates = new ArrayList<FileObject>();
203: try {
204: for (URL root : platform.getSourceRoots()) {
205: if (root.getProtocol().equals("jar")) { // NOI18N
206: // suppose zipped sources
207: File nbSrcF = archiveURLToFile(root);
208: if (!nbSrcF.exists()) {
209: continue;
210: }
211: NetBeansSourcesParser nbsp;
212: try {
213: nbsp = NetBeansSourcesParser
214: .getInstance(nbSrcF);
215: } catch (ZipException e) {
216: if (!quiet) {
217: Util.err
218: .annotate(
219: e,
220: ErrorManager.UNKNOWN,
221: nbSrcF
222: + " does not seem to be a valid ZIP file.",
223: null, null, null); // NOI18N
224: Util.err.notify(
225: ErrorManager.INFORMATIONAL, e);
226: }
227: continue;
228: }
229: if (nbsp == null) {
230: continue;
231: }
232: String pathInZip = nbsp.findSourceRoot(cnb);
233: if (pathInZip == null) {
234: continue;
235: }
236: URL u = new URL(root, pathInZip);
237: FileObject entryFO = URLMapper
238: .findFileObject(u);
239: if (entryFO != null) {
240: candidates.add(entryFO);
241: }
242: } else {
243: // Does not resolve nbjunit and similar from ZIPped
244: // sources. Not a big issue since the default distributed
245: // sources do not contain them anyway.
246: String relPath = resolveSpecialNBSrcPath(binaryRoot);
247: if (relPath == null) {
248: continue;
249: }
250: URL url = new URL(root, relPath);
251: FileObject dir = URLMapper.findFileObject(url);
252: if (dir != null) {
253: candidates.add(dir);
254: } // others dirs are currently resolved by o.n.m.apisupport.project.queries.SourceForBinaryImpl
255: }
256: }
257: } catch (IOException ex) {
258: throw new AssertionError(ex);
259: }
260: return candidates
261: .toArray(new FileObject[candidates.size()]);
262: }
263:
264: public void addChangeListener(ChangeListener l) {
265: // start listening on NbPlatform
266: changeSupport.addChangeListener(l);
267: if (!alreadyListening) {
268: platform.addPropertyChangeListener(this );
269: alreadyListening = true;
270: }
271: }
272:
273: public void removeChangeListener(ChangeListener l) {
274: changeSupport.removeChangeListener(l);
275: if (!changeSupport.hasListeners()) {
276: platform.removePropertyChangeListener(this );
277: alreadyListening = false;
278: }
279: }
280:
281: public void propertyChange(PropertyChangeEvent evt) {
282: if (NbPlatform.PROP_SOURCE_ROOTS.equals(evt
283: .getPropertyName())) {
284: changeSupport.fireChange();
285: }
286: }
287:
288: }
289:
290: private static String resolveSpecialNBSrcPath(URL binaryRoot)
291: throws MalformedURLException {
292: String binaryRootS = binaryRoot.toExternalForm();
293: String result = null;
294: if (binaryRootS.startsWith("jar:file:")) { // NOI18N
295: if (binaryRootS
296: .endsWith("/modules/org-netbeans-modules-nbjunit.jar!/")) { // NOI18N
297: result = "xtest/nbjunit/src/"; // NOI18N
298: } else if (binaryRootS
299: .endsWith("/modules/org-netbeans-modules-nbjunit-ide.jar!/")) { // NOI18N
300: result = "xtest/nbjunit/ide/src/"; // NOI18N
301: } else if (binaryRootS
302: .endsWith("/modules/ext/insanelib.jar!/")) { // NOI18N
303: result = "performance/insanelib/src/"; // NOI18N
304: } else {
305: result = null;
306: }
307: }
308: return result;
309: }
310:
311: private static File archiveURLToFile(final URL archiveURL) {
312: return new File(URI.create(FileUtil.getArchiveFile(archiveURL)
313: .toExternalForm()));
314: }
315:
316: public static final class NetBeansSourcesParser {
317:
318: /** Zip file to instance map. */
319: private static final Map<File, NetBeansSourcesParser> instances = new HashMap<File, NetBeansSourcesParser>();
320:
321: private static final String NBBUILD_ENTRY = "nbbuild/"; // NOI18N
322:
323: private Map<String, String> cnbToPrjDir;
324: private final ZipFile nbSrcZip;
325: private final String zipNBRoot;
326:
327: /**
328: * May return <code>null</code> if the given zip is not a valid
329: * NetBeans sources zip.
330: */
331: public static NetBeansSourcesParser getInstance(File nbSrcZip)
332: throws ZipException, IOException {
333: NetBeansSourcesParser nbsp = instances.get(nbSrcZip);
334: if (nbsp == null) {
335: ZipFile nbSrcZipFile = new ZipFile(nbSrcZip);
336: String zipNBRoot = NetBeansSourcesParser
337: .findNBRoot(nbSrcZipFile);
338: if (zipNBRoot != null) {
339: nbsp = new NetBeansSourcesParser(nbSrcZipFile,
340: zipNBRoot);
341: instances.put(nbSrcZip, nbsp);
342: }
343: }
344: return nbsp;
345: }
346:
347: NetBeansSourcesParser(ZipFile nbSrcZip, String zipNBRoot) {
348: this .nbSrcZip = nbSrcZip;
349: this .zipNBRoot = zipNBRoot;
350: }
351:
352: String findSourceRoot(final String cnb) {
353: if (cnbToPrjDir == null) {
354: try {
355: doScanZippedNetBeansOrgSources();
356: } catch (IOException ex) {
357: Util.err.notify(ErrorManager.WARNING, ex);
358: }
359: }
360: return cnbToPrjDir.get(cnb);
361: }
362:
363: private static String findNBRoot(final ZipFile nbSrcZip) {
364: String nbRoot = null;
365: for (Enumeration<? extends ZipEntry> en = nbSrcZip
366: .entries(); en.hasMoreElements();) {
367: ZipEntry entry = (ZipEntry) en.nextElement();
368: if (!entry.isDirectory()) {
369: continue;
370: }
371: String name = entry.getName();
372: if (!name.equals(NBBUILD_ENTRY)
373: && !(name.endsWith(NBBUILD_ENTRY) && name
374: .substring(name.indexOf('/') + 1)
375: .equals(NBBUILD_ENTRY))) {
376: continue;
377: }
378: ZipEntry xmlEntry = nbSrcZip.getEntry(name
379: + "nbproject/project.xml"); // NOI18N
380: if (xmlEntry != null) {
381: nbRoot = name.substring(0, name.length()
382: - NBBUILD_ENTRY.length());
383: break;
384: }
385: }
386: return nbRoot;
387: }
388:
389: private void doScanZippedNetBeansOrgSources()
390: throws IOException {
391: cnbToPrjDir = new HashMap<String, String>();
392: for (Enumeration<? extends ZipEntry> en = nbSrcZip
393: .entries(); en.hasMoreElements();) {
394: ZipEntry entry = en.nextElement();
395: if (!entry.isDirectory()) {
396: continue;
397: }
398: String path = entry.getName().substring(0,
399: entry.getName().length() - 1); // remove last slash
400: if (this .zipNBRoot != null
401: && (!path.startsWith(this .zipNBRoot) || path
402: .equals(this .zipNBRoot))) {
403: continue;
404: }
405: StringTokenizer st = new StringTokenizer(path, "/"); // NOI18N
406: if (st.countTokens() > /*ModuleList.DEPTH_NB_ALL*/3) {
407: continue;
408: }
409: String name = path.substring(path.lastIndexOf('/') + 1,
410: path.length());
411: if (ModuleList.EXCLUDED_DIR_NAMES.contains(name)) {
412: // #61579: known to not be project dirs, so skip to save time.
413: continue;
414: }
415: // XXX should read src.dir from properties
416: ZipEntry src = nbSrcZip.getEntry(entry.getName()
417: + "src/"); // NOI18N
418: if (src == null || !src.isDirectory()) {
419: continue;
420: }
421:
422: ZipEntry projectXML = nbSrcZip.getEntry(entry.getName()
423: + "nbproject/project.xml"); // NOI18N
424: if (projectXML == null) {
425: continue;
426: }
427: String cnb = parseCNB(projectXML);
428: if (cnb != null) {
429: cnbToPrjDir.put(cnb, entry.getName() + "src/"); // NOI18N
430: }
431: }
432: }
433:
434: private String parseCNB(final ZipEntry projectXML)
435: throws IOException {
436: Document doc;
437: InputStream is = nbSrcZip.getInputStream(projectXML);
438: try {
439: doc = XMLUtil.parse(new InputSource(is), false, true,
440: null, null);
441: } catch (SAXException e) {
442: throw (IOException) new IOException(projectXML + ": "
443: + e.toString()).initCause(e); // NOI18N
444: } finally {
445: is.close();
446: }
447: Element docel = doc.getDocumentElement();
448: Element type = Util.findElement(docel, "type",
449: "http://www.netbeans.org/ns/project/1"); // NOI18N
450: String cnb = null;
451: if (Util.findText(type).equals(
452: "org.netbeans.modules.apisupport.project")) { // NOI18N
453: Element cfg = Util.findElement(docel, "configuration",
454: "http://www.netbeans.org/ns/project/1"); // NOI18N
455: Element data = Util.findElement(cfg, "data",
456: NbModuleProjectType.NAMESPACE_SHARED); // NOI18N
457: if (data != null) {
458: cnb = Util.findText(Util.findElement(data,
459: "code-name-base",
460: NbModuleProjectType.NAMESPACE_SHARED)); // NOI18N
461: }
462: }
463: return cnb;
464: }
465:
466: }
467:
468: }
|