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.javadoc.search;
043:
044: import java.beans.PropertyChangeEvent;
045:
046: import java.beans.PropertyChangeListener;
047:
048: import java.net.URL;
049: import java.util.ArrayList;
050: import java.util.Arrays;
051: import java.util.HashSet;
052: import java.util.Iterator;
053: import java.util.LinkedList;
054: import java.util.List;
055: import java.util.Set;
056: import java.util.StringTokenizer;
057:
058: import java.io.Reader;
059: import java.io.IOException;
060: import java.io.BufferedReader;
061: import java.io.InputStreamReader;
062: import javax.swing.event.ChangeEvent;
063:
064: import javax.swing.event.ChangeListener;
065: import javax.swing.text.html.HTMLEditorKit;
066: import javax.swing.text.html.HTML;
067: import javax.swing.text.html.parser.ParserDelegator;
068: import javax.swing.text.MutableAttributeSet;
069:
070: import org.netbeans.api.java.classpath.ClassPath;
071: import org.netbeans.spi.java.classpath.support.ClassPathSupport;
072:
073: import org.openide.filesystems.FileObject;
074:
075: import org.netbeans.api.java.classpath.GlobalPathRegistry;
076: import org.netbeans.api.java.classpath.GlobalPathRegistryEvent;
077:
078: import org.netbeans.api.java.classpath.GlobalPathRegistryListener;
079:
080: import org.netbeans.api.java.queries.JavadocForBinaryQuery;
081: import org.openide.ErrorManager;
082: import org.openide.filesystems.URLMapper;
083: import org.openide.util.Lookup;
084:
085: /**
086: * Class which is able to serve index files of Javadoc for all
087: * currently used Javadoc documentation sets.
088: * @author Petr Hrebejk
089: */
090: public class JavadocRegistry implements GlobalPathRegistryListener,
091: ChangeListener, PropertyChangeListener {
092:
093: private static JavadocRegistry INSTANCE;
094:
095: private GlobalPathRegistry regs;
096: private ArrayList listeners;
097: private Set/*<JavadocForBinaryQuery.Result>*/results;
098: private ClassPath docRoots;
099: private Set/*ClassPath*/classpaths;
100:
101: /** Creates a new instance of JavadocRegistry */
102: private JavadocRegistry() {
103: this .regs = GlobalPathRegistry.getDefault();
104: this .regs.addGlobalPathRegistryListener(this );
105: }
106:
107: public static synchronized JavadocRegistry getDefault() {
108: if (INSTANCE == null) {
109: INSTANCE = new JavadocRegistry();
110: }
111: return INSTANCE;
112: }
113:
114: /** Returns Array of the Javadoc Index roots
115: */
116: public FileObject[] getDocRoots() {
117: synchronized (this ) {
118: if (this .docRoots != null) {
119: return this .docRoots.getRoots();
120: }
121: }
122: //XXX must be called out of synchronized block to prevent
123: // deadlock. throwCache is called under the ProjectManager.mutex
124: // write lock and Project's SFBQI requires the ProjectManager.mutex readLock
125: Set/*<ClassPath>*/_classpaths = new HashSet/*<ClassPath>*/();
126: Set/*<JavadocForBinaryQuery.Result>*/_results = new HashSet/*<JavadocForBinaryQuery.Result>*/();
127: Set/*<URL>*/s = readRoots(this , _classpaths, _results);
128: synchronized (this ) {
129: if (this .docRoots == null) {
130: this .docRoots = ClassPathSupport
131: .createClassPath((URL[]) s.toArray(new URL[s
132: .size()]));
133: this .classpaths = _classpaths;
134: this .results = _results;
135: registerListeners(this , _classpaths, _results,
136: this .docRoots);
137: }
138: return this .docRoots.getRoots();
139: }
140: }
141:
142: public JavadocSearchType findSearchType(FileObject apidocRoot) {
143: String encoding = getDocEncoding(apidocRoot);
144: Lookup.Result result = Lookup.getDefault().lookup(
145: new Lookup.Template(JavadocSearchType.class));
146: for (Iterator it = result.allInstances().iterator(); it
147: .hasNext();) {
148: JavadocSearchType jdst = (JavadocSearchType) it.next();
149: if (jdst.accepts(apidocRoot, encoding)) {
150: return jdst;
151: }
152: }
153: return null;
154: }
155:
156: // Private methods ---------------------------------------------------------
157:
158: private static Set/*<FileObject>*/readRoots(JavadocRegistry jdr,
159: Set/*<ClassPath>*/classpaths,
160: Set/*<JavadocForBinaryQuery.Result>*/results) {
161:
162: Set roots = new HashSet();
163: List paths = new LinkedList();
164: paths.addAll(jdr.regs.getPaths(ClassPath.COMPILE));
165: paths.addAll(jdr.regs.getPaths(ClassPath.BOOT));
166: for (Iterator it = paths.iterator(); it.hasNext();) {
167: ClassPath ccp = (ClassPath) it.next();
168: classpaths.add(ccp);
169: //System.out.println("CCP " + ccp );
170: List/*<ClassPath.Entry>*/ccpRoots = ccp.entries();
171:
172: for (Iterator it2 = ccpRoots.iterator(); it2.hasNext();) {
173: ClassPath.Entry ccpRoot = (ClassPath.Entry) it2.next();
174: //System.out.println(" CCPR " + ccpRoot.getURL());
175: JavadocForBinaryQuery.Result result = JavadocForBinaryQuery
176: .findJavadoc(ccpRoot.getURL());
177: results.add(result);
178: URL[] jdRoots = result.getRoots();
179: roots.addAll(Arrays.asList(jdRoots));
180: }
181: }
182: //System.out.println("roots=" + roots);
183: return roots;
184: }
185:
186: private static void registerListeners(JavadocRegistry jdr,
187: Set/*<ClassPath>*/classpaths,
188: Set/*<JavadocForBinaryQuery.Result>*/results,
189: ClassPath docRoots) {
190:
191: for (Iterator it = classpaths.iterator(); it.hasNext();) {
192: ClassPath cpath = (ClassPath) it.next();
193: cpath.addPropertyChangeListener(jdr);
194: }
195: for (Iterator it = results.iterator(); it.hasNext();) {
196: JavadocForBinaryQuery.Result result = (JavadocForBinaryQuery.Result) it
197: .next();
198: result.addChangeListener(jdr);
199: }
200:
201: docRoots.addPropertyChangeListener(jdr);
202:
203: }
204:
205: public void pathsAdded(GlobalPathRegistryEvent event) {
206: this .throwCache();
207: this .fireChange();
208: }
209:
210: public void pathsRemoved(GlobalPathRegistryEvent event) {
211: this .throwCache();
212: this .fireChange();
213: }
214:
215: public void propertyChange(PropertyChangeEvent event) {
216: if (ClassPath.PROP_ENTRIES.equals(event.getPropertyName())
217: || event.getSource() == this .docRoots) {
218: this .throwCache();
219: this .fireChange();
220: }
221: }
222:
223: public void stateChanged(javax.swing.event.ChangeEvent e) {
224: this .throwCache();
225: this .fireChange();
226: }
227:
228: public synchronized void addChangeListener(ChangeListener l) {
229: assert l != null : "Listener can not be null."; //NOI18N
230: if (this .listeners == null) {
231: this .listeners = new ArrayList();
232: }
233: this .listeners.add(l);
234: }
235:
236: public synchronized void removeChangeListener(ChangeListener l) {
237: assert l != null : "Listener can not be null."; //NOI18N
238: if (this .listeners == null) {
239: return;
240: }
241: this .listeners.remove(l);
242: }
243:
244: private void fireChange() {
245: Iterator it = null;
246: synchronized (this ) {
247: if (this .listeners == null) {
248: return;
249: }
250: it = ((ArrayList) this .listeners.clone()).iterator();
251: }
252: ChangeEvent event = new ChangeEvent(this );
253: while (it.hasNext()) {
254: ((ChangeListener) it.next()).stateChanged(event);
255: }
256: }
257:
258: private synchronized void throwCache() {
259: //Unregister itself from classpaths, not interested in events
260: if (this .classpaths != null) {
261: for (Iterator it = this .classpaths.iterator(); it.hasNext();) {
262: ClassPath cp = (ClassPath) it.next();
263: cp.removePropertyChangeListener(this );
264: it.remove();
265: }
266: }
267: //Unregister itself from results, not interested in events
268: if (this .results != null) {
269: for (Iterator it = this .results.iterator(); it.hasNext();) {
270: JavadocForBinaryQuery.Result result = (JavadocForBinaryQuery.Result) it
271: .next();
272: result.removeChangeListener(this );
273: it.remove();
274: }
275: }
276: //Unregister listener from docRoots
277: if (this .docRoots != null) {
278: this .docRoots.removePropertyChangeListener(this );
279: this .docRoots = null;
280: }
281: }
282:
283: private String getDocEncoding(FileObject root) {
284: assert root != null && root.isFolder();
285: FileObject fo = root.getFileObject("index-all.html"); //NOI18N
286: if (fo == null) {
287: fo = root.getFileObject("index-files"); //NOI18N
288: if (fo == null) {
289: return null;
290: }
291: fo = fo.getFileObject("index-1.html"); //NOI18N
292: if (fo == null) {
293: return null;
294: }
295: }
296: ParserDelegator pd = new ParserDelegator();
297: try {
298: BufferedReader in = new BufferedReader(
299: new InputStreamReader(fo.getInputStream()));
300: EncodingCallback ecb = new EncodingCallback(in);
301: try {
302: pd.parse(in, ecb, true);
303: } catch (IOException ioe) {
304: //Do nothing
305: } finally {
306: in.close();
307: }
308: return ecb.getEncoding();
309: } catch (IOException ioe) {
310: ErrorManager.getDefault().annotate(ioe, fo.toString());
311: ErrorManager.getDefault().notify(ioe);
312: }
313: return null;
314: }
315:
316: private static class EncodingCallback extends
317: HTMLEditorKit.ParserCallback {
318:
319: private Reader in;
320: private String encoding;
321:
322: public EncodingCallback(Reader in) {
323: this .in = in;
324: }
325:
326: public String getEncoding() {
327: return this .encoding;
328: }
329:
330: public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a,
331: int pos) {
332: if (t == HTML.Tag.META) {
333: String value = (String) a
334: .getAttribute(HTML.Attribute.CONTENT);
335: if (value != null) {
336: StringTokenizer tk = new StringTokenizer(value, ";"); // NOI18N
337: while (tk.hasMoreTokens()) {
338: String str = tk.nextToken().trim();
339: if (str.startsWith("charset")) { //NOI18N
340: str = str.substring(7).trim();
341: if (str.charAt(0) == '=') {
342: this .encoding = str.substring(1).trim();
343: try {
344: this .in.close();
345: } catch (IOException ioe) {/*Ignore it*/
346: }
347: return;
348: }
349: }
350: }
351: }
352: }
353: }
354:
355: public void handleStartTag(javax.swing.text.html.HTML.Tag t,
356: javax.swing.text.MutableAttributeSet a, int pos) {
357: if (t == HTML.Tag.BODY) {
358: try {
359: this .in.close();
360: } catch (IOException ioe) {/*Ignore it*/
361: }
362: }
363: }
364: }
365:
366: }
|