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.javahelp;
043:
044: import java.io.*;
045: import java.net.*;
046: import java.util.*;
047:
048: import org.openide.modules.InstalledFileLocator;
049: import org.openide.modules.ModuleInfo;
050: import org.openide.util.Exceptions;
051: import org.openide.util.Lookup;
052: import org.openide.util.NbBundle;
053:
054: /** Handler & connection cribbed from NbResourceStreamHandler.
055: * @author Jesse Glick
056: */
057: final class NbDocsStreamHandler extends URLStreamHandler {
058:
059: public static final class Factory implements
060: URLStreamHandlerFactory {
061: public URLStreamHandler createURLStreamHandler(String protocol) {
062: if (protocol.equals("nbdocs")) { // NOI18N
063: return new NbDocsStreamHandler();
064: } else {
065: return null;
066: }
067: }
068: }
069:
070: /** Make a URLConnection for nbdocs: URLs.
071: * @param u the URL
072: * @throws IOException if the wrong protocol
073: * @return the connection
074: */
075: protected URLConnection openConnection(URL u) throws IOException {
076: if (u.getProtocol().equals("nbdocs")) { // NOI18N
077: return new NbDocsURLConnection(u);
078: } else {
079: throw new IOException("mismatched protocol"); // NOI18N
080: }
081: }
082:
083: @Override
084: protected synchronized InetAddress getHostAddress(URL u) {
085: if (u.getProtocol().equals("nbdocs")) { // NOI18N
086: return null;
087: } else {
088: return super .getHostAddress(u);
089: }
090: }
091:
092: /** A URL connection that reads from the docs classloader.
093: */
094: private static final class NbDocsURLConnection extends
095: URLConnection {
096:
097: /** underlying URL connection
098: */
099: private URLConnection real = null;
100:
101: /** any associated exception while handling
102: */
103: private IOException exception = null;
104:
105: /** Make the connection.
106: * @param u URL to connect to
107: */
108: public NbDocsURLConnection(URL u) {
109: super (u);
110: }
111:
112: /** Connect to the URL.
113: * Actually look up and open the underlying connection.
114: * @throws IOException for the usual reasons
115: */
116: public synchronized void connect() throws IOException {
117: if (exception != null) {
118: IOException e = exception;
119: exception = null;
120: throw e;
121: }
122: if (!connected) {
123: String host = url.getHost();
124: if (host.length() > 0) {
125: ModuleInfo moduleInfo = findModule(host);
126: if (moduleInfo != null) {
127: if (!moduleInfo.isEnabled()) {
128: URL info = new URL(
129: "nbdocs:/org/netbeans/modules/javahelp/resources/notEnabledModule.html"); // NOI18N
130: String moduleName = moduleInfo
131: .getDisplayName();
132: real = new InfoURLConnection(info,
133: moduleName);
134: real.connect();
135: connected = true;
136: return;
137: }
138: } else {
139: URL info = new URL(
140: "nbdocs:/org/netbeans/modules/javahelp/resources/notInstalledModule.html"); // NOI18N
141: String moduleName = ""; // NOI18N
142: try {
143: moduleName = NbBundle.getMessage(
144: NbDocsStreamHandler.class, host);
145: } catch (MissingResourceException exc) {
146: moduleName = host;
147: }
148: real = new InfoURLConnection(info, moduleName);
149: real.connect();
150: connected = true;
151: return;
152: }
153: }
154: String resource = url.getFile();
155: if (resource.startsWith("/"))
156: resource = resource.substring(1); //NOI18N
157: URL target;
158: String ext, basename;
159: int index = resource.lastIndexOf('.');
160: if (index != -1 && index > resource.lastIndexOf('/')) {
161: ext = resource.substring(index + 1);
162: basename = resource.substring(0, index).replace(
163: '/', '.');
164: } else {
165: ext = null;
166: basename = resource.replace('/', '.');
167: }
168: try {
169: target = NbBundle.getLocalizedFile(basename, ext);
170: } catch (MissingResourceException mre) {
171: // OK, try file.
172: File f = InstalledFileLocator.getDefault().locate(
173: "docs/" + resource, null, true); // NOI18N
174: if (f != null) {
175: target = f.toURI().toURL();
176: } else {
177: IOException ioe = new IOException(
178: "cannot connect to " + url + ": " + mre);
179: ioe.initCause(mre);
180: Exceptions.attachLocalizedMessage(ioe, NbBundle
181: .getMessage(NbDocsStreamHandler.class,
182: "EXC_nbdocs_cannot_connect",
183: url));
184: throw ioe;
185: }
186: }
187: //System.err.println("loading from " + target);
188: real = target.openConnection();
189: real.connect();
190: connected = true;
191: }
192: }
193:
194: /** Searches for module with given code name.
195: * @param codeNameBase unique string base name of the module
196: * (without release number)
197: *
198: * @return module info of found module or null if module is not found
199: * (not installed).
200: * @deprecated will be replaced by similar method in Modules Open APIs in
201: * future releases
202: */
203: private static ModuleInfo findModule(String codeNameBase) {
204: Lookup.Result<ModuleInfo> modulesResult = Lookup
205: .getDefault().lookup(
206: new Lookup.Template<ModuleInfo>(
207: ModuleInfo.class));
208: for (ModuleInfo curInfo : modulesResult.allInstances()) {
209: if (curInfo.getCodeNameBase().equals(codeNameBase)) {
210: return curInfo;
211: }
212: }
213: return null;
214: }
215:
216: /** Maybe connect, if not keep track of the problem.
217: */
218: private void tryToConnect() {
219: if (connected || exception != null)
220: return;
221: try {
222: connect();
223: } catch (IOException ioe) {
224: exception = ioe;
225: }
226: }
227:
228: /** Get a URL header.
229: * @param n index of the header
230: * @return the header value
231: */
232: public String getHeaderField(int n) {
233: tryToConnect();
234: if (connected)
235: return real.getHeaderField(n);
236: else
237: return null;
238: }
239:
240: /** Get the name of a header.
241: * @param n the index
242: * @return the header name
243: */
244: public String getHeaderFieldKey(int n) {
245: tryToConnect();
246: if (connected)
247: return real.getHeaderFieldKey(n);
248: else
249: return null;
250: }
251:
252: /** Get a header by name.
253: * @param key the header name
254: * @return the value
255: */
256: public String getHeaderField(String key) {
257: tryToConnect();
258: if (connected)
259: return real.getHeaderField(key);
260: else
261: return null;
262: }
263:
264: /** Get an input stream on the connection.
265: * @throws IOException for the usual reasons
266: * @return a stream to the object
267: */
268: public InputStream getInputStream() throws IOException {
269: connect();
270: return real.getInputStream();
271: }
272:
273: /** Get an output stream on the object.
274: * @throws IOException for the usual reasons
275: * @return an output stream writing to it
276: */
277: public OutputStream getOutputStream() throws IOException {
278: connect();
279: return real.getOutputStream();
280: }
281:
282: /** Get the type of the content.
283: * @return the MIME type
284: */
285: public String getContentType() {
286: tryToConnect();
287: if (connected)
288: return real.getContentType();
289: else
290: return "application/octet-stream"; // NOI18N
291: }
292:
293: /** Get the length of content.
294: * @return the length in bytes
295: */
296: public int getContentLength() {
297: tryToConnect();
298: if (connected)
299: return real.getContentLength();
300: else
301: return 0;
302: }
303:
304: }
305:
306: /** A URL connection that reads from the info files. It displays
307: * help page when referred module is not enabled or installed.
308: * It also takes module display name from bundle when available.
309: * Module base name is key to retrieve module display name
310: * eg.: org.netbeans.modules.web.monitor=HTTP Monitor
311: */
312: private static final class InfoURLConnection extends URLConnection {
313: /** Provides input stream for this connection. */
314: private ByteArrayInputStream stream;
315: /** Module display name */
316: private String moduleName;
317:
318: /** Make the connection.
319: * @param u URL to connect to
320: */
321: public InfoURLConnection(URL u, String moduleName) {
322: super (u);
323: this .moduleName = moduleName;
324: }
325:
326: /** Connect to the URL.
327: * Actually look up and open the underlying connection.
328: * @throws IOException for the usual reasons
329: */
330: public synchronized void connect() throws IOException {
331: if (!connected) {
332: //Prepare data
333: InputStream is = url.openStream();
334: if (is != null) {
335: byte[] arr;
336: arr = readData(is);
337: String s1 = new String(arr, "UTF-8"); // NOI18N
338: String s2 = s1.replaceAll("\\{0\\}", moduleName); // NOI18N
339: arr = s2.getBytes("UTF-8");
340: stream = new ByteArrayInputStream(arr);
341: } else {
342: throw new IOException("Info file not found."); // NOI18N
343: }
344: connected = true;
345: }
346: }
347:
348: /** Reads all available data from input steram to byte array. It is workaround
349: * to avoid usage of InputStream.available which might be unreliable on URL. */
350: private byte[] readData(InputStream is) throws IOException {
351: int step = 4096;
352: byte[] buff = new byte[step];
353: byte[] sum = new byte[0];
354: byte[] result;
355: int len = -1, readLen = 0, allocLen = 0;
356:
357: for (;;) {
358: len = is.read(buff);
359: if (len == -1) {
360: result = new byte[readLen];
361: System.arraycopy(sum, 0, result, 0, readLen);
362: return result;
363: }
364: if (allocLen < (readLen + len)) {
365: byte[] tmp = new byte[sum.length];
366: System.arraycopy(sum, 0, tmp, 0, readLen);
367: sum = new byte[allocLen + step];
368: allocLen = allocLen + step;
369: System.arraycopy(tmp, 0, sum, 0, readLen);
370: }
371: System.arraycopy(buff, 0, sum, readLen, len);
372: readLen = readLen + len;
373: }
374: }
375:
376: /** Maybe connect, if not keep track of the problem.
377: */
378: private void tryToConnect() {
379: if (connected) {
380: return;
381: }
382: try {
383: connect();
384: } catch (IOException ioe) {
385: }
386: }
387:
388: /** Get an input stream on the connection.
389: * @throws IOException for the usual reasons
390: * @return a stream to the object
391: */
392: public InputStream getInputStream() throws IOException {
393: connect();
394: return stream;
395: }
396:
397: /** Get the type of the content.
398: * @return the MIME type
399: */
400: public String getContentType() {
401: return "text/html"; // NOI18N
402: }
403:
404: /** Get the length of content.
405: * @return the length in bytes
406: */
407: public int getContentLength() {
408: tryToConnect();
409: if (connected) {
410: return stream.available();
411: } else {
412: return 0;
413: }
414: }
415: }
416: }
|