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: package com.sun.rave.web.ui.util;
042:
043: import java.io.BufferedInputStream;
044: import java.io.File;
045: import java.io.FileInputStream;
046: import java.io.FileNotFoundException;
047: import java.io.FilterInputStream;
048: import java.io.IOException;
049: import java.io.InputStream;
050: import java.lang.reflect.InvocationTargetException;
051: import java.lang.reflect.Method;
052:
053: /**
054: * <p> This <code>InputStream</code> looks for lines beginning with
055: * "#include '<em>filename</em>'" where filename is the name of a file to
056: * include. It replaces the "#include" line with contents of the
057: * specified file. Any other line beginning with '#' is illegal.</p>
058: */
059: public class IncludeInputStream extends FilterInputStream {
060:
061: /**
062: * <p> Constructor.</p>
063: */
064: public IncludeInputStream(InputStream input) {
065: super (input);
066: }
067:
068: /**
069: * <p> This overriden method implements the include feature.</p>
070: *
071: * @return The next character.
072: */
073: public int read() throws IOException {
074: int intChar = -1;
075: if (redirStream != null) {
076: // We are already redirecting, delegate
077: intChar = redirStream.read();
078: if (intChar != -1) {
079: return intChar;
080: }
081:
082: // Found end of redirect file, stop delegating
083: redirStream = null;
084: }
085:
086: // Read next character
087: intChar = super .read();
088: char ch = (char) intChar;
089:
090: // If we were at the end of the line, check for new line w/ #
091: if (eol) {
092: // Check to see if we have a '#'
093: if (ch == '#') {
094: intChar = startInclude();
095: } else {
096: eol = false;
097: }
098: }
099:
100: // Flag EOL if we're at the end of a line
101: if ((ch == 0x0A) || (ch == 0x0D)) {
102: eol = true;
103: }
104:
105: return intChar;
106: }
107:
108: public int available() throws IOException {
109: return 0;
110: }
111:
112: public boolean markSupported() {
113: return false;
114: }
115:
116: public int read(byte[] bytes, int off, int len) throws IOException {
117: if (bytes == null) {
118: throw new NullPointerException();
119: } else if ((off < 0) || (off > bytes.length) || (len < 0)
120: || ((off + len) > bytes.length) || ((off + len) < 0)) {
121: throw new IndexOutOfBoundsException();
122: } else if (len == 0) {
123: return 0;
124: }
125:
126: int c = read();
127: if (c == -1) {
128: return -1;
129: }
130: bytes[off] = (byte) c;
131:
132: int i = 1;
133: try {
134: for (; i < len; i++) {
135: c = read();
136: if (c == -1) {
137: break;
138: }
139: if (bytes != null) {
140: bytes[off + i] = (byte) c;
141: }
142: }
143: } catch (IOException ee) {
144: ee.printStackTrace();
145: }
146: return i;
147: }
148:
149: /**
150: *
151: */
152: private int startInclude() throws IOException {
153: // We have a line beginning w/ '#', verify we have "#include"
154: char ch;
155: for (int count = 0; count < INCLUDE_LEN; count++) {
156: // look for include
157: ch = (char) super .read();
158: if (Character.toLowerCase(ch) != INCLUDE.charAt(count)) {
159: throw new RuntimeException("\"#include\" expected in "
160: + "IncludeInputStream.");
161: }
162: }
163:
164: // Skip whitespace...
165: ch = (char) super .read();
166: while ((ch == ' ') || (ch == '\t')) {
167: ch = (char) super .read();
168: }
169:
170: // Skip '"' or '\''
171: if ((ch == '"') || (ch == '\'')) {
172: ch = (char) super .read();
173: }
174:
175: // Read the file name
176: StringBuffer buf = new StringBuffer("");
177: while ((ch != '"') && (ch != '\'') && (ch != 0x0A)
178: && (ch != 0x0D) && (ch != -1)) {
179: buf.append(ch);
180: ch = (char) super .read();
181: }
182:
183: // Skip ending '"' or '\'', if any
184: if ((ch == '"') || (ch == '\'')) {
185: ch = (char) super .read();
186: }
187:
188: // Get the file name...
189: String filename = buf.toString();
190:
191: // Determine if we're in a JSF environment...
192: if (FACES_CONTEXT != null) {
193: // We are... get a context root relative path...
194: filename = convertRelativePath(filename);
195: }
196: File file = new File(filename);
197: // Make sure file exists (don't check read, let it throw an exception)
198: if (file.exists()) {
199: // Open the included file
200: redirStream = new IncludeInputStream(
201: new BufferedInputStream(new FileInputStream(file)));
202: } else {
203: // Check Classpath?
204: InputStream stream = Thread.currentThread()
205: .getContextClassLoader().getResourceAsStream(
206: filename);
207: if (stream == null) {
208: stream = Thread.currentThread().getContextClassLoader()
209: .getResourceAsStream("/" + filename);
210: }
211: if (stream == null) {
212: throw new FileNotFoundException(filename);
213: }
214: redirStream = new IncludeInputStream(
215: new BufferedInputStream(stream));
216: }
217:
218: // Read the first character from the file to return
219: return redirStream.read();
220: }
221:
222: /**
223: * <p> This method converts a context-root relative path to the actual
224: * path using the ServletContext or PortletContext. This requires
225: * the application to be running in a Servlet or Portlet
226: * environment... and further requires that it be running in JSF
227: * environment (which is used to access the Servlet or Portlet
228: * Context).</p>
229: *
230: * @param filename The relative filename to convert to a full path.
231: *
232: * @return The full path based on the app's context root.
233: */
234: protected String convertRelativePath(String filename) {
235: // NOTE: This method uses reflection to avoid build/runtime
236: // NOTE: dependencies on JSF, this method is only used if the
237: // NOTE: FacesContext class is found in the classpath.
238:
239: // Check for the file in docroot
240: Method method = null;
241: Object ctx = null;
242: String newFilename = null;
243: try {
244: // The following should work w/ a ServletContext or PortletContext
245: // Get the FacesContext...
246: method = FACES_CONTEXT.getMethod("getCurrentInstance",
247: (Class[]) null);
248: ctx = method.invoke((Object) null, (Object[]) null);
249:
250: // Get the ExternalContext...
251: method = ctx.getClass().getMethod("getExternalContext",
252: (Class[]) null);
253: ctx = method.invoke(ctx, (Object[]) null);
254:
255: // Get actual underlying external context...
256: method = ctx.getClass().getMethod("getContext",
257: (Class[]) null);
258: ctx = method.invoke(ctx, (Object[]) null);
259:
260: // Get the real path using the ServletContext/PortletContext
261: method = ctx.getClass().getMethod("getRealPath",
262: GET_REAL_PATH_ARGS);
263: newFilename = (String) method.invoke(ctx,
264: new Object[] { filename });
265: if (!(new File(newFilename)).exists()) {
266: // The file doesn't exist, fall back to absolute path
267: newFilename = filename;
268: }
269: } catch (NoSuchMethodException ex) {
270: throw new RuntimeException(ex);
271: } catch (IllegalAccessException ex) {
272: throw new RuntimeException(ex);
273: } catch (InvocationTargetException ex) {
274: throw new RuntimeException(ex);
275: }
276: return newFilename;
277: }
278:
279: /**
280: * <p> Simple test case (requires a test file).</p>
281: */
282: public static void main(String args[]) {
283: try {
284: IncludeInputStream stream = new IncludeInputStream(
285: new FileInputStream(args[0]));
286: int ch = '\n';
287: while (ch != -1) {
288: System.out.print((char) ch);
289: ch = stream.read();
290: }
291: } catch (Exception ex) {
292: ex.printStackTrace();
293: }
294: }
295:
296: private boolean eol = true;
297: private IncludeInputStream redirStream = null;
298:
299: private static final Class[] GET_REAL_PATH_ARGS = new Class[] { String.class };
300:
301: private static final String INCLUDE = "include";
302: private static final int INCLUDE_LEN = INCLUDE.length();
303:
304: private static Class FACES_CONTEXT;
305:
306: static {
307: try {
308: FACES_CONTEXT = Class
309: .forName("javax.faces.context.FacesContext");
310: } catch (Exception ex) {
311: // Ignore, this just means we're not in a JSF environment
312: FACES_CONTEXT = null;
313: }
314: }
315: }
|