001: /*
002: * Helma License Notice
003: *
004: * The contents of this file are subject to the Helma License
005: * Version 2.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://adele.helma.org/download/helma/license.txt
008: *
009: * Copyright 1998-2003 Helma Software. All Rights Reserved.
010: *
011: * $RCSfile$
012: * $Author: root $
013: * $Revision: 8604 $
014: * $Date: 2007-09-28 15:16:38 +0200 (Fre, 28 Sep 2007) $
015: */
016:
017: package helma.doc;
018:
019: import helma.framework.repository.Resource;
020: import helma.util.StringUtils;
021:
022: import java.awt.Point;
023: import java.io.IOException;
024: import java.io.InputStreamReader;
025: import java.io.Reader;
026: import java.util.List;
027: import java.util.ArrayList;
028:
029: import org.mozilla.javascript.*;
030:
031: /**
032: *
033: */
034: public class DocFunction extends DocResourceElement {
035:
036: private int startLine;
037:
038: protected DocFunction(String name, Resource res, DocElement parent,
039: int type, int lineno) {
040: super (name, res, type);
041: this .parent = parent;
042: this .startLine = lineno;
043: }
044:
045: /**
046: * creates a new independent DocFunction object of type ACTION
047: */
048: public static DocFunction newAction(Resource res)
049: throws IOException {
050: return newAction(res, null);
051: }
052:
053: /**
054: * creates a new DocFunction object of type ACTION connected to another DocElement
055: */
056: public static DocFunction newAction(Resource res, DocElement parent)
057: throws IOException {
058: String name = res.getBaseName();
059: String[] lines = StringUtils.splitLines(res.getContent());
060: DocFunction func = new DocFunction(name, res, parent, ACTION, 1);
061: String rawComment = "";
062: Token[] tokens = parseTokens(res);
063: rawComment = Util.extractString(lines, getPoint(tokens[0]),
064: getPoint(tokens[1]));
065: rawComment = Util.chopComment(rawComment);
066: func.parseComment(rawComment);
067: func.content = res.getContent();
068: return func;
069: }
070:
071: /**
072: * reads a function file and creates independent DocFunction objects of type FUNCTION
073: */
074: public static DocFunction[] newFunctions(Resource res)
075: throws IOException {
076: return newFunctions(res, null);
077: }
078:
079: /**
080: * reads a function file and creates DocFunction objects of type FUNCTION
081: * connected to another DocElement.
082: */
083: public static DocFunction[] newFunctions(Resource res,
084: DocElement parent) throws IOException {
085: String[] lines = StringUtils.splitLines(res.getContent());
086: Token[] tokens = parseTokens(res);
087: List list = new ArrayList();
088: scanFunctions(lines, tokens, list, res, parent, 0,
089: tokens.length);
090: return (DocFunction[]) list.toArray(new DocFunction[0]);
091: }
092:
093: private static void scanFunctions(String[] lines, Token[] tokens,
094: List list, Resource res, DocElement parent, int start,
095: int end) {
096: // Token token = null;
097: Token lastToken = new Token(Token.EMPTY, "", 0, 0);
098: // Point marker;
099:
100: String lastNameString = null;
101: String functionName = null;
102: String context = null;
103: String comment = "";
104:
105: for (int i = start; i < end - 1; i++) {
106:
107: // store the position of the last token
108: Point marker = getPoint(lastToken);
109: // now get a new token
110: Token token = tokens[i];
111: // flag for dropping private functions
112: boolean dropFunction = false;
113:
114: if (token.type == Token.EOL) {
115:
116: String c = Util.extractString(lines, marker,
117: getPoint(token));
118: if (c.startsWith("/**"))
119: comment = c;
120:
121: } else if (token.type == Token.LC) {
122:
123: // when we come across a left brace outside of a function,
124: // we store the current string of the stream, it might be
125: // a function object declaration
126: // e.g. HttpClient = { func1:function()...}
127: context = token.string;
128:
129: } else if (token.type == Token.RC && context != null) {
130:
131: // when we come across a right brace outside of a function,
132: // we reset the current context cache
133: context = null;
134:
135: } else if (token.type == Token.THIS) {
136:
137: if (parent instanceof DocFunction)
138: lastNameString = parent.getName() + ".prototype";
139: // this may be the start of a name chain declaring a function
140: // e.g. Number.prototype.functionName = function() { }
141: // marker = getPoint(token);
142: // marker.x -= (5);
143:
144: } else if (token.type == Token.NAME) {
145:
146: // store all names, the last one before a function
147: // declaration may be used as its name
148:
149: if (lastToken.type != Token.DOT) {
150:
151: lastNameString = token.string;
152:
153: // this may be the start of a name chain declaring a function
154: // e.g. Number.prototype.functionName = function() { }
155: marker = getPoint(token);
156: marker.x -= (token.string.length() + 1);
157:
158: } else {
159:
160: // token in front of the name was a dot, so we connect the
161: // names that way
162: lastNameString += "." + token.string;
163:
164: }
165:
166: } else if (token.type == Token.FUNCTION) {
167:
168: // store the end of the function word
169: Point p = getPoint(token);
170:
171: // look at the next token:
172: Token peekToken = tokens[i + 1];
173:
174: // depending of the style of the declaration we already have all we need
175: // or need to fetch the name from the next token:
176: if (peekToken.type == Token.NAME) {
177:
178: // if the token after FUNCTION is NAME, it's the usual function
179: // declaration like this: function abc() {}
180:
181: // set the pointer for the start of the actual function body
182: // to the letter f of the function word
183: marker = p;
184: marker.x -= 9;
185:
186: // set stream to next token, so that name of the
187: // function is the stream's current string
188: token = tokens[++i];
189: functionName = token.string;
190: } else {
191:
192: // it's a different kind of function declaration.
193: // the function name is the last found NAME-token
194: // if context is set, prepend it to the function name
195: functionName = (context != null) ? context + "."
196: + lastNameString : lastNameString;
197:
198: }
199:
200: DocFunction theFunction = null;
201: if (!dropFunction) {
202: // create the function object
203: DocElement par = parent instanceof DocFunction ? parent.parent
204: : parent;
205: theFunction = newFunction(functionName, res, par,
206: token.lineno + 1);
207: theFunction.parseComment(comment);
208: list.add(theFunction);
209: }
210: // reset comment
211: comment = "";
212:
213: // subloop on the tokenstream: find the parameters of a function
214: while (i < end && token.type != Token.RP) {
215: token = tokens[++i];
216: if (token.type == Token.NAME
217: && theFunction.type == FUNCTION) {
218: // add names of parameter only for functions, not for macros or actions
219: theFunction.addParameter(token.string);
220: }
221: }
222:
223: // subloop on the tokenstream: find the closing right bracket of the function
224: token = tokens[++i];
225: int j = i + 1;
226: int level = (token.type == Token.LC) ? 1 : 0;
227: while (i < end && level > 0) {
228: // regular expression syntax is troublesome for the TokenStream
229: // we don't need them here, so we just ignore such an error
230: try {
231: token = tokens[++i];
232: } catch (Exception anything) {
233: continue;
234: }
235: if (token.type == Token.LC) {
236: level++;
237: } else if (token.type == Token.RC) {
238: level--;
239: }
240: }
241:
242: if (dropFunction)
243: continue;
244:
245: // parse function body for nested functions
246: scanFunctions(lines, tokens, list, res, theFunction, j,
247: i);
248: // set the function body, starting at the beginning of the first line
249: marker.x = 0;
250: theFunction.content = Util.extractString(lines, marker,
251: getPoint(token));
252:
253: } // end if
254:
255: lastToken = token;
256:
257: } // end while
258:
259: }
260:
261: private static DocFunction newFunction(String funcName,
262: Resource res, DocElement parent, int lineno) {
263: if (funcName.endsWith("_action")) {
264: return new DocFunction(funcName, res, parent, ACTION,
265: lineno);
266: } else if (funcName.endsWith("_macro")) {
267: return new DocFunction(funcName, res, parent, MACRO, lineno);
268: } else {
269: return new DocFunction(funcName, res, parent, FUNCTION,
270: lineno);
271: }
272: }
273:
274: /**
275: * Creates a rhino token stream for a given file.
276: * @param res the JS Resource
277: * @return a TokenStream wrapper
278: * @throws java.io.IOException if an I/O exception was raised
279: */
280: protected static Token[] parseTokens(Resource res)
281: throws IOException {
282: Reader reader = new InputStreamReader(res.getInputStream());
283: CompilerEnvirons compilerEnv = new CompilerEnvirons();
284: compilerEnv.initFromContext(Context.getCurrentContext());
285: compilerEnv.setGenerateDebugInfo(true);
286: compilerEnv.setGeneratingSource(true);
287: compilerEnv.setOptimizationLevel(-1);
288: ErrorReporter errorReporter = Context.getCurrentContext()
289: .getErrorReporter();
290: Parser parser = new Parser(compilerEnv, errorReporter);
291: return parser.parseTokens(reader, res.getName(), 0);
292: }
293:
294: /**
295: * Returns a pointer to the current position in the TokenStream
296: * @param token the TokenStream
297: * @return the current position
298: */
299: protected static Point getPoint(Token token) {
300: return new Point(token.offset, token.lineno);
301: }
302:
303: /**
304: * from helma.framework.IPathElement. All macros, templates, actions etc
305: * have the same prototype.
306: */
307: public java.lang.String getPrototype() {
308: return "docfunction";
309: }
310:
311: /**
312: * Get the first line of this function within the containing resource.
313: * @return the first line of the function
314: */
315: public int getStartLine() {
316: return startLine;
317: }
318: }
|