001: ///////////////////////////////
002: //Makumba, Makumba tag library
003: //Copyright (C) 2000-2003 http://www.makumba.org
004: //
005: //This library is free software; you can redistribute it and/or
006: //modify it under the terms of the GNU Lesser General Public
007: //License as published by the Free Software Foundation; either
008: //version 2.1 of the License, or (at your option) any later version.
009: //
010: //This library is distributed in the hope that it will be useful,
011: //but WITHOUT ANY WARRANTY; without even the implied warranty of
012: //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: //Lesser General Public License for more details.
014: //
015: //You should have received a copy of the GNU Lesser General Public
016: //License along with this library; if not, write to the Free Software
017: //Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: //
019: //-------------
020: //$Id: JavaParseData.java 1828 2007-10-15 12:08:00Z rosso_nero $
021: //$Name$
022: /////////////////////////////////////
023:
024: package org.makumba.analyser.engine;
025:
026: import java.io.File;
027: import java.util.ArrayList;
028: import java.util.Arrays;
029: import java.util.Collections;
030: import java.util.HashSet;
031: import java.util.Hashtable;
032: import java.util.List;
033: import java.util.StringTokenizer;
034: import java.util.regex.Matcher;
035: import java.util.regex.Pattern;
036:
037: import org.makumba.analyser.interfaces.JavaAnalyzer;
038: import org.makumba.commons.NamedResourceFactory;
039: import org.makumba.commons.NamedResources;
040:
041: /**
042: * This class performs a rudimentary detection of Java syntax elements in a Java class.
043: *
044: * @author Rudolf Mayer
045: */
046: public class JavaParseData implements
047: SourceSyntaxPoints.PreprocessorClient {
048:
049: private class DefinitionPoint implements Comparable {
050: String className;
051:
052: int position;
053:
054: public DefinitionPoint(String className, int position) {
055: this .className = className;
056: this .position = position;
057: }
058:
059: public int compareTo(Object arg0) {
060: return (new Integer(position).compareTo(new Integer(
061: ((DefinitionPoint) arg0).position)));
062: }
063:
064: public String toString() {
065: return position + ":" + className;
066: }
067: }
068:
069: public static boolean isCommentSyntaxPoint(String type) {
070: return (Arrays.asList(JavaCommentPatternNames).contains(type));
071: }
072:
073: public static boolean isClassUsageSyntaxPoint(String type) {
074: return (Arrays.asList(JavaClassUsagePatternNames)
075: .contains(type));
076: }
077:
078: public static boolean isPrimitiveType(String type) {
079: return primitiveTypes.contains(type);
080: }
081:
082: /** Cache of all page analyses. */
083: public static int analyzedPages = NamedResources.makeStaticCache(
084: "Java page analyses", new NamedResourceFactory() {
085: private static final long serialVersionUID = 1L;
086:
087: public Object getHashObject(Object o) {
088: Object[] o1 = (Object[]) o;
089: return ((String) o1[0])
090: + o1[1].getClass().getName();
091: }
092:
093: public Object makeResource(Object o, Object hashName)
094: throws Throwable {
095: Object[] o1 = (Object[]) o;
096: return new JavaParseData((String) o1[0],
097: (JavaAnalyzer) o1[1], (String) o1[2]);
098: }
099: }, true);
100:
101: private static String[] JavaCommentPatternNames = {
102: "JavaBlockComment", "JavaDocComment", "JavaLineComment" };
103:
104: private static Pattern[] JavaCommentPatterns;
105:
106: private static String[] JavaClassUsagePatternNames = {
107: "JavaVariableDefinition", "JavaNewInstance",
108: "JavaParameter", "JavaClassCast", "JavaMethodReturn",
109: "JavaThrows", "JavaCatch", "JavaExtends" };
110:
111: private static Pattern[] JavaClassUsagePatterns;
112:
113: /** The patterns used to parse the page. */
114: private static Pattern JavaDocCommentPattern,
115: JavaBlockCommentPattern, JavaLineCommentPattern;
116:
117: private static Pattern JavaModifierPattern,
118: JavaReservedWordPattern;
119:
120: private static Pattern JavaStringLiteral;
121:
122: private static Pattern JavaVariableDefinition, JavaNewInstance,
123: JavaParameter, JavaClassCast, JavaMethodReturn, JavaThrows,
124: JavaCatch, JavaExtends;
125:
126: private static Pattern JavaImportPackage;
127:
128: private static Pattern JavaMethodInvocation;
129:
130: private static Pattern MakumbaFormHandler;
131:
132: private static List primitiveTypes = Arrays.asList(new String[] {
133: "char", "byte", "short", "int", "long", "boolean", "float",
134: "double", "void" });
135:
136: /** Initialiser for the class variables. */
137: static {
138: initPatterns();
139: }
140:
141: private static void initPatterns() {
142: // FIXME: this is not correct, could also start with _, $, ..
143: String identifier = "\\w[\\w|\\d]*";
144: String spaces = "[\\s]*";
145: String minOneSpaces = "\\s" + spaces;
146:
147: JavaDocCommentPattern = Pattern.compile("/\\*\\*.*?[^\\*]\\*/",
148: Pattern.DOTALL);
149:
150: JavaBlockCommentPattern = Pattern.compile("/\\*[^\\*]*\\*/",
151: Pattern.DOTALL);
152: JavaLineCommentPattern = Pattern.compile("//.*$",
153: Pattern.MULTILINE);
154:
155: JavaModifierPattern = Pattern
156: .compile("(public )|(private )|(protected )|(transient )|(static )|(void )");
157: JavaReservedWordPattern = Pattern
158: .compile("(class )|(int )|(boolean )|(double )|(float )|(short )|(long )|(byte )|(for )|(for\\()|(do )|(do\\{)|(while )|(while\\()|(switch )|(case )|(return )|(if )|(if\\()|(else )|(import\\s)|(package\\s)|(super\\.)|(super \\()|(super )|(inner\\.)|(outer\\.)|(extends )|(throws )");
159: JavaImportPackage = Pattern.compile("(package" + minOneSpaces
160: + "\\w[\\w\\d\\.]*" + spaces + ";)|(import"
161: + minOneSpaces + "\\w[\\w\\d\\.]*" + "[\\.\\*]?"
162: + spaces + ";)");
163: JavaStringLiteral = Pattern.compile("\"[^\"]*\"");
164:
165: // find class usage.
166: JavaNewInstance = Pattern.compile("new" + minOneSpaces
167: + identifier + "[(| ]");
168: JavaVariableDefinition = Pattern.compile("[" + identifier
169: + "\\.]*" + identifier + minOneSpaces + identifier
170: + spaces + "[;|=]");
171: JavaParameter = Pattern.compile("[(|,]" + spaces + identifier
172: + minOneSpaces + identifier);
173: JavaClassCast = Pattern.compile("\\(" + spaces + identifier
174: + spaces + "\\)" + spaces + identifier);
175: // has problems with new|void, should be excluded ?
176: JavaMethodReturn = Pattern.compile(spaces + identifier
177: + minOneSpaces + identifier + spaces + "\\(");
178: JavaThrows = Pattern.compile("throws" + spaces + identifier);
179: JavaCatch = Pattern.compile("catch" + spaces + identifier);
180: JavaExtends = Pattern.compile("extends" + spaces + identifier);
181:
182: JavaMethodInvocation = Pattern.compile(identifier + spaces
183: + "\\." + spaces + identifier + "\\(");
184:
185: MakumbaFormHandler = Pattern
186: .compile("on_(new|add|edit|delete)\\w+\\(");
187:
188: JavaCommentPatterns = new Pattern[] { JavaBlockCommentPattern,
189: JavaDocCommentPattern, JavaLineCommentPattern };
190: JavaClassUsagePatterns = new Pattern[] {
191: JavaVariableDefinition, JavaNewInstance, JavaParameter,
192: JavaClassCast, JavaMethodReturn, JavaThrows, JavaCatch,
193: JavaExtends };
194:
195: }
196:
197: /**
198: * Return the pageData of the class at the given path in the given webapp. This is the only way for clients of this class to obtain instances of
199: * JavaPageData
200: */
201: static public JavaParseData getParseData(String webappRoot,
202: String path, JavaAnalyzer an) {
203: Object arg[] = { webappRoot + path, an, path };
204: return (JavaParseData) NamedResources.getStaticCache(
205: analyzedPages).getResource(arg);
206: }
207:
208: /** The analyzer plugged in. */
209: JavaAnalyzer analyzer;
210:
211: /** The Java file path */
212: File file;
213:
214: /** The holder of the analysis status, and partial results. */
215: Object holder;
216:
217: /** The syntax points of this page. */
218: SourceSyntaxPoints syntaxPoints;
219:
220: /** The Java URI, for debugging purposes. */
221: String uri;
222:
223: /** The set of in this class imported packages. */
224: HashSet<String> importedPackages = new HashSet<String>();
225:
226: private Hashtable<String, String> importedClasses = new Hashtable<String, String>();
227:
228: private String viewedClass = null;
229:
230: private String super Class = null;
231:
232: private Hashtable<String, ArrayList<DefinitionPoint>> definedObjects = new Hashtable<String, ArrayList<DefinitionPoint>>();
233:
234: /** Private constructor, construction can only be made by getParseData(). */
235: protected JavaParseData(String path, JavaAnalyzer an, String uri) {
236: // initPatterns(); // uncomment this if you want to test patterns
237: this .file = new File(path);
238: this .uri = uri;
239: this .analyzer = an;
240: }
241:
242: /**
243: * This method will perform the analysis if not performed already, or if the file has changed. the method is synchronized, so other accesses are
244: * blocked if the current access determines that an analysis needs be performed
245: *
246: * @param initStatus
247: * an initial status to be passed to the JavaAnalyzer. for example, the pageContext for an example-based analyzer
248: */
249: public synchronized Object getAnalysisResult(Object initStatus) {
250: if (getSyntaxPoints() == null || !getSyntaxPoints().unchanged())
251: try {
252: parse(initStatus);
253: } catch (Error e) {
254: holder = e;
255: throw e;
256: } catch (RuntimeException re) {
257: holder = re;
258: throw re;
259: }
260: return holder;
261: }
262:
263: /**
264: * Gets the imported packages found in this java class.
265: *
266: * @return A collection of Strings denoting package names.
267: */
268: public HashSet<String> getImportedPackages() {
269: return importedPackages;
270: }
271:
272: public Hashtable<String, String> getImportedClasses() {
273: return importedClasses;
274: }
275:
276: public String[] getCommentPatternNames() {
277: return JavaCommentPatternNames;
278: }
279:
280: public Pattern[] getCommentPatterns() {
281: return JavaCommentPatterns;
282: }
283:
284: public String[] getLiteralPatternNames() {
285: return new String[] { "JavaStringLiteral" };
286: }
287:
288: public Pattern[] getLiteralPatterns() {
289: return new Pattern[] { JavaStringLiteral };
290: }
291:
292: public Pattern getIncludePattern() {
293: return null;
294: }
295:
296: public String getIncludePatternName() {
297: return null;
298: }
299:
300: public String getDefinedObjectClassName(String objectName,
301: int position) {
302: ArrayList points = (ArrayList) definedObjects.get(objectName);
303: DefinitionPoint maxPoint = null;
304: if (points != null) {
305: for (int i = 0; i < points.size(); i++) {
306: DefinitionPoint current = (DefinitionPoint) points
307: .get(i);
308: if (current.position < position) {
309: maxPoint = current;
310: } else {
311: break;
312: }
313: }
314: if (maxPoint != null) {
315: return maxPoint.className;
316: }
317: }
318: return null;
319: }
320:
321: /**
322: * @return Returns the superClass.
323: */
324: public String getSuperClass() {
325: return super Class;
326: }
327:
328: /**
329: * @return Returns the viewedClass.
330: */
331: public String getViewedClass() {
332: return viewedClass;
333: }
334:
335: /** Gets the collection of syntax points. */
336: public SourceSyntaxPoints getSyntaxPoints() {
337: return syntaxPoints;
338: }
339:
340: /** Parses the file. */
341: void parse(Object initStatus) {
342: long start = new java.util.Date().getTime();
343: syntaxPoints = new SourceSyntaxPoints(file, this );
344:
345: holder = analyzer.makeStatusHolder(initStatus);
346:
347: //
348: treatJavaImports(syntaxPoints.getContent(), analyzer);
349:
350: // treat sting literals
351: treatJavaStringLiterals(syntaxPoints.getContent(), analyzer);
352:
353: // treat Java Modifiers
354: treatJavaModifiers(syntaxPoints.getContent(), analyzer);
355:
356: // treat Java Reserved Words
357: treatReservedWords(syntaxPoints.getContent(), analyzer);
358:
359: treatClassUsage(syntaxPoints.getContent(), analyzer);
360:
361: treatMethodUsage(syntaxPoints.getContent(), analyzer);
362:
363: treatMakumbaHandler(syntaxPoints.getContent(), analyzer);
364:
365: holder = analyzer.endPage(holder);
366:
367: java.util.logging.Logger.getLogger(
368: "org.makumba." + "javaparser.time").info(
369: "analysis of " + uri + " took "
370: + (new java.util.Date().getTime() - start)
371: + " ms");
372: }
373:
374: public void treatInclude(int position, String includeDirective,
375: SourceSyntaxPoints host) {
376: }
377:
378: /** Go thru the java import statments in the class. */
379: void treatJavaImports(String content, JavaAnalyzer an) {
380: Matcher m = JavaImportPackage.matcher(content);
381: while (m.find()) {
382: syntaxPoints.addSyntaxPoints(m.start(), m.end(),
383: "JavaImport", null);
384: String sub = content.substring(m.start(), m.end() - 1)
385: .trim();
386: String importedPackage = sub.split("\\s")[1];
387: if (sub.endsWith(".*")
388: || content.substring(m.start(), m.end()).trim()
389: .startsWith("package")) {
390: if (importedPackage.startsWith("package")) {
391: importedPackage = importedPackage.substring(
392: "package".length()).trim();
393: }
394: if (importedPackage.endsWith(".*")) {
395: importedPackage = importedPackage.substring(0,
396: importedPackage.length() - 2);
397: }
398: importedPackages.add(importedPackage + ".");
399: } else {
400: String className = sub
401: .substring(sub.lastIndexOf(".") + 1);
402: importedClasses.put(className, importedPackage);
403: }
404: }
405: }
406:
407: /** Go thru the reserved words in the class. */
408: void treatReservedWords(String content, JavaAnalyzer an) {
409: Matcher m = JavaReservedWordPattern.matcher(content);
410: while (m.find()) {
411: syntaxPoints.addSyntaxPoints(m.start(), m.end(),
412: "JavaReservedWord", null);
413: String s = content.substring(m.start(), m.end()).trim();
414: if (s.equals("class") && viewedClass == null) {
415: String c = content.substring(m.start()).trim();
416: c = c.substring(c.indexOf(" ")).trim();
417: c = c.substring(0, c.indexOf(" "));
418: viewedClass = c;
419: } else if (s.equals("extends") && super Class == null) {
420: String c = content.substring(m.start()).trim();
421: c = c.substring(c.indexOf(" ")).trim();
422: c = c.substring(0, c.indexOf(" "));
423: super Class = c;
424: }
425: }
426: }
427:
428: void treatClassUsage(String content, JavaAnalyzer an) {
429: for (int i = 0; i < JavaClassUsagePatterns.length; i++) {
430: Matcher m = JavaClassUsagePatterns[i].matcher(content);
431: while (m.find()) {
432: String substring = content
433: .substring(m.start(), m.end());
434: if (JavaClassUsagePatterns[i] != JavaMethodReturn
435: || !(substring.trim().startsWith("new"))) {
436: String className = extractClassName(substring,
437: JavaClassUsagePatterns[i]);
438: int beginIndex = content.indexOf(className, m
439: .start());
440: int endIndex = beginIndex + className.length();
441: String s = content.substring(beginIndex, endIndex);
442: if (!isPrimitiveType(s)) {
443: SyntaxPoint end = syntaxPoints.addSyntaxPoints(
444: beginIndex, endIndex,
445: JavaClassUsagePatternNames[i], null);
446:
447: // add defined objects --> store object name, class name and position in file, to be able to
448: // retrieve objects with the same name, but from a different class (e.g. "String a" in method
449: // "b", and "Integer a" in method "c").
450: if ((JavaClassUsagePatterns[i] == JavaVariableDefinition || JavaClassUsagePatterns[i] == JavaParameter)
451: && substring.trim().indexOf("new ") == -1) {
452: String objectName = substring.trim()
453: .substring(
454: substring
455: .indexOf(className)
456: + className
457: .length())
458: .replace('=', ' ')
459: .replace(';', ' ').trim();
460: ArrayList<DefinitionPoint> currentContent = definedObjects
461: .get(objectName);
462: if (currentContent == null) {
463: currentContent = new ArrayList<DefinitionPoint>();
464: }
465: currentContent.add(new DefinitionPoint(
466: className, end.getPosition()));
467: Collections.sort(currentContent);
468: definedObjects.put(objectName,
469: currentContent);
470: java.util.logging.Logger.getLogger(
471: "org.makumba." + "javaparser")
472: .finest(
473: "Put defined object: "
474: + objectName
475: + ", values: "
476: + currentContent);
477: }
478: }
479: }
480: }
481: }
482: }
483:
484: private static String extractClassName(String code, Pattern pattern) {
485: code = code.replace('(', ' ').trim();
486: code = code.replace(')', ' ').trim();
487: code = code.replace(',', ' ').trim();
488:
489: StringTokenizer t = new StringTokenizer(code);
490: ArrayList<String> s = new ArrayList<String>();
491: while (t.hasMoreTokens()) {
492: s.add(t.nextToken());
493: }
494: String[] parts = s.toArray(new String[s.size()]);
495:
496: if (pattern == JavaVariableDefinition
497: || pattern == JavaParameter
498: || pattern == JavaMethodReturn
499: || pattern == JavaClassCast) {
500: return parts[0];
501: } else if (pattern == JavaNewInstance || pattern == JavaThrows
502: || pattern == JavaCatch || pattern == JavaExtends) {
503: return parts[1];
504: } else {
505: return "";
506: }
507: }
508:
509: /** Go thru the java String Literals in the class. */
510: void treatJavaStringLiterals(String content, JavaAnalyzer an) {
511: treatSimplePattern(content, an, JavaStringLiteral,
512: "JavaStringLiteral");
513: }
514:
515: /** Go thru the java modifiers in the class. */
516: void treatJavaModifiers(String content, JavaAnalyzer an) {
517: treatSimplePattern(content, an, JavaModifierPattern,
518: "JavaModifier");
519: }
520:
521: void treatMethodUsage(String content, JavaAnalyzer an) {
522: treatSimplePattern(content, an, JavaMethodInvocation,
523: "JavaMethodInvocation");
524: }
525:
526: void treatMakumbaHandler(String content, JavaAnalyzer an) {
527: treatSimplePattern(content, an, MakumbaFormHandler,
528: "MakumbaFormHandler");
529: }
530:
531: private void treatSimplePattern(String content, JavaAnalyzer an,
532: Pattern pattern, String SyntaxPointName) {
533: Matcher m = pattern.matcher(content);
534: while (m.find()) {
535: syntaxPoints.addSyntaxPoints(m.start(), m.end() - 1,
536: SyntaxPointName, null);
537: }
538: }
539:
540: } // end class
|