0001: package de.java2html.javasource;
0002:
0003: import java.io.BufferedReader;
0004: import java.io.File;
0005: import java.io.FileReader;
0006: import java.io.IOException;
0007: import java.io.InputStream;
0008: import java.io.InputStreamReader;
0009: import java.io.Reader;
0010: import java.io.StringReader;
0011: import java.net.URL;
0012: import java.util.Hashtable;
0013: import java.util.StringTokenizer;
0014:
0015: import de.java2html.options.JavaSourceConversionOptions;
0016: import de.java2html.util.IoUtilities;
0017:
0018: /**
0019: * Parses raw text to a {@link de.java2html.javasource.JavaSource} object. The
0020: * parser can not only handle grammatically correct Java source files but also
0021: * code snippets.
0022: *
0023: * <p>
0024: * (Parsing is done in multiple steps starting with raw text where every
0025: * character is classified as UNDEFINED and trying to find out more about it
0026: * step by step. There are some state machines used for parsing. They are hand
0027: * coded and quite complicated. The parser seems to be very stable, as I have
0028: * not been reported a single bug now for about two years.)
0029: *
0030: * <p>
0031: * For questions, suggestions, bug-reports, enhancement-requests etc. I may be
0032: * contacted at: <a href="mailto:markus@jave.de">markus@jave.de</a>
0033: *
0034: * The Java2html home page is located at: <a href="http://www.java2html.de">
0035: * http://www.java2html.de</a>
0036: *
0037: * @author <a href="mailto:markus@jave.de">Markus Gebhard</a>
0038: *
0039: * <code>Copyright (C) Markus Gebhard 2000-2003
0040: *
0041: * This program is free software; you can redistribute it and/or
0042: * * modify it under the terms of the GNU General Public License
0043: * * as published by the Free Software Foundation; either version 2
0044: * * of the License, or (at your option) any later version.
0045: *
0046: * This program is distributed in the hope that it will be useful,
0047: * * but WITHOUT ANY WARRANTY; without even the implied warranty of
0048: * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0049: * * GNU General Public License for more details.
0050: *
0051: * You should have received a copy of the GNU General Public License
0052: * * along with this program; if not, write to the Free Software
0053: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.</code>
0054: */
0055: public class JavaSourceParser {
0056: /** The source code being converted */
0057: private JavaSource source;
0058:
0059: /** For faster access to source.getCode() */
0060: private String sourceCode;
0061:
0062: /** For faster access to source.getClassification() */
0063: private JavaSourceType[] sourceTypes;
0064:
0065: private JavaSourceConversionOptions options;
0066:
0067: /** Delimiters for numeric values. */
0068: private final static String NUM_DELIMITERS = " \t\n\r()[]{};:+-/\\*!?#%&|<>=^,";
0069:
0070: /** Delimiters for finding data types and keywords. */
0071: private final static String DELIMITERS = " \t\n\r()[]{};:.+-/\\*!?#%&|<>=^";
0072:
0073: /** Characters automatically classified as being empty (type==BACKGROUND) */
0074: private final static String EMPTY_STR = " \t\n\r\f";
0075:
0076: private final static String[] PRIMITIVE_DATATYPES = { "boolean",
0077: "byte", "char", "double", "float", "int", "long", "short",
0078: "void" };
0079:
0080: /*
0081: * As defined by Java Language Specification SE §3,
0082: */
0083: private final static String[] JAVA_KEYWORDS = { "assert",
0084: "abstract", "default", "if", "private", "this", "do",
0085: "implements", "protected", "throw", "break", "import",
0086: "public", "throws", "else", "instanceof", "return",
0087: "transient", "case", "extends", "try", "catch", "final",
0088: "interface", "static", "finally", "strictfp", "volatile",
0089: "class", "native", "super", "while", "const", "for", "new",
0090: "strictfp", "switch", "continue", "goto", "package",
0091: "synchronized", "threadsafe", "null", "true", "false",
0092: //Enum keyword from JDK1.5 (TypeSafe Enums)
0093: "enum", "@interface" };
0094:
0095: private final static String[] JAVADOC_KEYWORDS = { "@author",
0096: "@beaninfo", "@docRoot", "@deprecated", "@exception",
0097: "@link", "@param", "@return", "@see", "@serial",
0098: "@serialData", "@serialField", "@since", "@throws",
0099: "@version",
0100: //new in JDK1.4
0101: "@linkplain", "@inheritDoc", "@value",
0102: //from iDoclet
0103: "@pre", "@post", "@inv",
0104: //from disy
0105: "@published" };
0106:
0107: /** Hashtables for fast access to JavaDoc keywords (tags) */
0108: private static Hashtable tableJavaDocKeywords;
0109:
0110: /** Hashtables for fast access to keywords */
0111: private static Hashtable tableJavaKeywords;
0112:
0113: /* States for the first state machine */
0114: private final static short PARSESTATE_FINISHED = -1;
0115: private final static short COD = 0; //CODE
0116: private final static short CAC = 1; //CODE AWAIT COMMENT
0117: private final static short CL = 2; //COMMENT LINE
0118: private final static short CBJ1 = 3; //COMMENT BLOCK or COMMENT JAVADOC 1
0119: private final static short CBJ2 = 4; //COMMENT BLOCK or COMMENT JAVADOC 1
0120: private final static short CB = 5; //COMMENT BLOCK
0121: private final static short CBA = 6; //COMMENT BLOCK AWAIT END
0122: private final static short CJ = 7; //COMMENT JAVADOC
0123: private final static short CJA = 8; //COMMENT JAVADOC AWAIT END
0124: private final static short QU = 9; //QUOTE
0125: private final static short QUA = 10; //QUOTE AWAIT \"
0126: private final static short CH1 = 11; //
0127: private final static short CH2 = 12; //
0128: private final static short CH3 = 13; //
0129: private final static short CH4 = 14; //
0130: private final static short CH5 = 15; //
0131: private final static short CH6 = 16; //
0132:
0133: /* Additional states for the second state machine */
0134: private final static short PARSESTATE_START = 0;
0135: private final static short PARSESTATE_NEUTRAL = 1;
0136: private final static short PARSESTATE_DA = 2;
0137: private final static short PARSESTATE_NA = 3;
0138: private final static short PARSESTATE_EXP = 4;
0139: private final static short PARSESTATE_HEX = 5;
0140: private final static short PARSESTATE_HIA = 6;
0141:
0142: /** Counter for this and that (parseThree()?) */
0143: private int counter;
0144:
0145: /** EOT=End of text */
0146: private final static char EOT = (char) -1;
0147:
0148: /* State informations for state machine one */
0149: private short parseState;
0150: private int parseSourcePos;
0151: private int parseTypePos;
0152:
0153: public JavaSourceParser() {
0154: this (JavaSourceConversionOptions.getDefault());
0155: }
0156:
0157: public JavaSourceParser(JavaSourceConversionOptions options) {
0158: buildTables();
0159: this .options = options;
0160: }
0161:
0162: /**
0163: * Baut aus den statischen String-Arrays die Hashtabellen auf, mit denen die
0164: * Keywords im Quelltext gesucht werden.
0165: */
0166: private synchronized void buildTables() {
0167: if (tableJavaDocKeywords != null && tableJavaKeywords != null) {
0168: return;
0169: }
0170:
0171: tableJavaDocKeywords = new Hashtable(
0172: (int) (JAVADOC_KEYWORDS.length * 1.5));
0173: for (int i = 0; i < JAVADOC_KEYWORDS.length; ++i) {
0174: tableJavaDocKeywords.put(JAVADOC_KEYWORDS[i],
0175: JAVADOC_KEYWORDS[i]);
0176: }
0177:
0178: tableJavaKeywords = new Hashtable(
0179: (int) (JAVA_KEYWORDS.length * 1.5));
0180: for (int i = 0; i < JAVA_KEYWORDS.length; ++i) {
0181: tableJavaKeywords.put(JAVA_KEYWORDS[i], JAVA_KEYWORDS[i]);
0182: }
0183: }
0184:
0185: private final static boolean isEmpty(char ch) {
0186: return (EMPTY_STR.indexOf(ch) != -1);
0187: }
0188:
0189: private boolean isNumberDelimiter(char ch) {
0190: return (NUM_DELIMITERS.indexOf(ch) != -1);
0191: }
0192:
0193: private final static int indexOf(char ch, String s, int start,
0194: int end) {
0195: if (end < start)
0196: return -1;
0197:
0198: for (int i = start; i <= end; ++i) {
0199: if (s.charAt(i) == ch)
0200: return i;
0201: }
0202:
0203: return -1;
0204: }
0205:
0206: //public void parse(){
0207: // sourceCode=source.getCode();
0208: // sourceTypes=new JavaSourceType[sourceCode.length()];
0209: // parseOne();
0210: // parseTwo();
0211: // parseThree();
0212: // source.setClassification(sourceTypes);
0213: //}
0214:
0215: public JavaSource parse(File file) throws IOException {
0216: source = parse(new FileReader(file));
0217: source.setFileName(file.getName());
0218: return source;
0219: }
0220:
0221: public JavaSource parse(String rawText) {
0222: if (rawText == null) {
0223: throw new NullPointerException();
0224: }
0225: try {
0226: return parse(new StringReader(rawText));
0227: } catch (IOException e) {
0228: System.err
0229: .println("Unexpected exception while parsing raw text: "
0230: + e);
0231: return new JavaSource("");
0232: }
0233: }
0234:
0235: public JavaSource parse(URL url) throws IOException {
0236: InputStream inputStream = null;
0237: try {
0238: inputStream = url.openStream();
0239: return parse(inputStream);
0240: } finally {
0241: IoUtilities.close(inputStream);
0242: }
0243: }
0244:
0245: public JavaSource parse(InputStream stream) throws IOException {
0246: return parse(new InputStreamReader(stream));
0247: }
0248:
0249: public JavaSource parse(Reader reader) throws IOException {
0250: if (reader == null) {
0251: throw new IllegalArgumentException("reader may not be null");
0252: }
0253: try {
0254: sourceCode = readPlainSource(reader);
0255: } finally {
0256: IoUtilities.close(reader);
0257: }
0258: replaceTabs();
0259:
0260: sourceTypes = new JavaSourceType[sourceCode.length()];
0261: source = new JavaSource(sourceCode);
0262: source.setClassification(sourceTypes);
0263:
0264: parseOne();
0265: parseTwo();
0266: parseThree();
0267: parseFour();
0268:
0269: doStatistics();
0270:
0271: return source;
0272: }
0273:
0274: private void parseFour() {
0275: boolean isInsideAnnotation = false;
0276: for (int i = 0; i < sourceTypes.length; ++i) {
0277: if (!isInsideAnnotation
0278: && sourceTypes[i] == JavaSourceType.CODE
0279: && sourceCode.charAt(i) == '@') {
0280: isInsideAnnotation = true;
0281: sourceTypes[i] = JavaSourceType.ANNOTATION;
0282: } else if (isInsideAnnotation
0283: && sourceTypes[i] == JavaSourceType.CODE
0284: && (Character.isJavaIdentifierPart(sourceCode
0285: .charAt(i)) || sourceCode.charAt(i) == '.')) {
0286: sourceTypes[i] = JavaSourceType.ANNOTATION;
0287: } else {
0288: isInsideAnnotation = false;
0289: }
0290: }
0291: }
0292:
0293: /**
0294: * Gathers statistical information from the source code. After parsing this
0295: * is quite easy and maybe it is useful for others. lineCount is needed for
0296: * the html converter.
0297: */
0298: private void doStatistics() {
0299: int index = 0;
0300: source.getStatistic().clear();
0301: source.getStatistic().setCharacterCount(sourceCode.length());
0302: int linesContainingAnything = 0;
0303:
0304: if (sourceCode.length() == 0) {
0305: source.getStatistic().setLineCount(0);
0306: } else {
0307: StringTokenizer st = new StringTokenizer(sourceCode,
0308: "\n\r", true);
0309: while (st.hasMoreTokens()) {
0310: String line = st.nextToken();
0311:
0312: if (line.charAt(0) == '\r') {
0313: ++index;
0314: } else if (line.charAt(0) == '\n') {
0315: ++index;
0316: source.getStatistic().setLineCount(
0317: source.getStatistic().getLineCount() + 1);
0318: } else {
0319: ++linesContainingAnything;
0320: statistics(line, index);
0321: index += line.length();
0322: }
0323: }
0324: source.getStatistic().setLineCount(
0325: source.getStatistic().getLineCount() + 1);
0326: }
0327:
0328: //some empty lines without any were not counted
0329: source.getStatistic().setEmptyLineCount(
0330: source.getStatistic().getLineCount()
0331: - linesContainingAnything);
0332: }
0333:
0334: private void statistics(String line, int start) {
0335: if (line.length() > source.getStatistic().getMaxLineLength()) {
0336: source.getStatistic().setMaxLineLength(line.length());
0337: }
0338:
0339: int end = start + line.length();
0340:
0341: boolean containsCode = false;
0342: boolean containsComment = false;
0343:
0344: for (int i = start; i < end; ++i) {
0345: if (sourceTypes[i] == JavaSourceType.CODE
0346: || sourceTypes[i] == JavaSourceType.KEYWORD
0347: || sourceTypes[i] == JavaSourceType.CODE_TYPE
0348: || sourceTypes[i] == JavaSourceType.CHAR_CONSTANT
0349: || sourceTypes[i] == JavaSourceType.NUM_CONSTANT) {
0350: containsCode = true;
0351: if (containsComment)
0352: break;
0353: } else if (sourceTypes[i] == JavaSourceType.COMMENT_BLOCK
0354: || sourceTypes[i] == JavaSourceType.COMMENT_LINE
0355: || sourceTypes[i] == JavaSourceType.JAVADOC
0356: || sourceTypes[i] == JavaSourceType.JAVADOC_KEYWORD) {
0357: containsComment = true;
0358: if (containsCode)
0359: break;
0360: }
0361: }
0362:
0363: if (containsCode)
0364: source.getStatistic().setCodeLineCount(
0365: source.getStatistic().getCodeLineCount() + 1);
0366: if (containsComment)
0367: source.getStatistic().setCommentLineCount(
0368: source.getStatistic().getCommentLineCount() + 1);
0369: if (!containsCode && !containsComment)
0370: source.getStatistic().setEmptyLineCount(
0371: source.getStatistic().getEmptyLineCount() + 1);
0372: }
0373:
0374: private String readPlainSource(Reader reader) throws IOException {
0375: return readPlainSource(new BufferedReader(reader));
0376: }
0377:
0378: private String readPlainSource(BufferedReader reader)
0379: throws IOException {
0380:
0381: StringBuffer sb = new StringBuffer();
0382: String line;
0383: while ((line = reader.readLine()) != null) {
0384: sb.append(line);
0385: sb.append("\r\n");
0386: }
0387: if (sb.length() > 0) {
0388: sb.setLength(sb.length() - 2);
0389: }
0390: return sb.toString();
0391: // while (true){
0392: // char[] buffer = new char[256];
0393: // int length = reader.read(buffer, 0 , 256);
0394: // if (length<=0){
0395: // break;
0396: // }
0397: // sb.append(buffer, 0, length);
0398: // }
0399: // //Newlines are converted to "\r\n" for compatibility with eclipse
0400: // styledtext!!!
0401: // return NewLineNormalizer.normalize(sb.toString(), "\r\n");
0402: }
0403:
0404: /**
0405: * Preprocessing: Replaces all tabs (\t) by 'tabs' space characters.
0406: */
0407: private void replaceTabs() {
0408: char[] t = new char[options.getTabSize()];
0409: for (int i = 0; i < options.getTabSize(); ++i) {
0410: t[i] = ' ';
0411: }
0412:
0413: StringBuffer sb = new StringBuffer(
0414: (int) (sourceCode.length() * 1.3));
0415: for (int i = 0; i < sourceCode.length(); ++i) {
0416: char ch = sourceCode.charAt(i);
0417: if (ch == '\t') {
0418: sb.append(t);
0419: } else {
0420: sb.append(ch);
0421: }
0422: }
0423:
0424: sourceCode = sb.toString();
0425: }
0426:
0427: /**
0428: * First step of parsing. All characters are classified 'UNDEFINED' and we
0429: * try to divide this into: CODE, CHAR_CONSTANT, COMMENT_LINE, COMMENT_BLOCK,
0430: * COMMENT_JAVADOC, BACKGROUND and QUOTE This is done by a quite complicate
0431: * state machine.
0432: */
0433: private void parseOne() {
0434: parseState = COD;
0435: parseSourcePos = 0;
0436: parseTypePos = 0;
0437:
0438: while (parseState != PARSESTATE_FINISHED) {
0439: parseOneDo();
0440: }
0441: }
0442:
0443: /**
0444: * State-machine for classifying the code to: CODE, CHAR_CONSTANT,
0445: * COMMENT_LINE, COMMENT_BLOCK, COMMENT_JAVADOC, BACKGROUND and QUOTE
0446: *
0447: * Note: It works - don't ask me how! If you want to know more about it all
0448: * you can do is taking a sheet of paper (or more) and a pencil and try to
0449: * draw the state machine :-)
0450: */
0451: private void parseOneDo() {
0452: char ch = EOT;
0453: if (sourceCode.length() > parseSourcePos) {
0454: ch = sourceCode.charAt(parseSourcePos++);
0455: }
0456:
0457: switch (parseState) {
0458: case COD:
0459: if (ch == EOT) {
0460: parseState = PARSESTATE_FINISHED;
0461: return;
0462: }
0463: if (ch == '/') {
0464: parseState = CAC;
0465: return;
0466: }
0467: if (ch == '"') {
0468: sourceTypes[parseTypePos++] = JavaSourceType.STRING;
0469: parseState = QU;
0470: return;
0471: }
0472: if (ch == '\'') {
0473: parseState = CH1;
0474: return;
0475: }
0476: if (isEmpty(ch)) {
0477: sourceTypes[parseTypePos++] = JavaSourceType.BACKGROUND;
0478: return;
0479: }
0480:
0481: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0482: return;
0483: case CAC:
0484: if (ch == EOT) {
0485: parseState = PARSESTATE_FINISHED;
0486: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0487: return;
0488: }
0489: if (ch == '/') {
0490: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_LINE;
0491: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_LINE;
0492: parseState = CL;
0493: return;
0494: }
0495: if (ch == '*') {
0496: parseState = CBJ1;
0497: return;
0498: }
0499: if (isEmpty(ch)) {
0500: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0501: sourceTypes[parseTypePos++] = JavaSourceType.BACKGROUND;
0502: parseState = COD;
0503: return;
0504: }
0505:
0506: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0507: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0508: parseState = COD;
0509: return;
0510: case CL:
0511: if (ch == EOT) {
0512: parseState = PARSESTATE_FINISHED;
0513: return;
0514: }
0515: if (ch == '\n' || ch == '\r') {
0516: sourceTypes[parseTypePos++] = JavaSourceType.BACKGROUND;
0517: //ggf. durch COMMENT_LINE ersetzen
0518: parseState = COD;
0519: return;
0520: }
0521: if (isEmpty(ch)) {
0522: sourceTypes[parseTypePos++] = JavaSourceType.BACKGROUND;
0523: return;
0524: }
0525: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_LINE;
0526: return;
0527: case CB:
0528: if (ch == EOT) {
0529: parseState = PARSESTATE_FINISHED;
0530: return;
0531: }
0532: if (ch == '*') {
0533: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_BLOCK;
0534: parseState = CBA;
0535: return;
0536: }
0537: if (isEmpty(ch)) {
0538: sourceTypes[parseTypePos++] = JavaSourceType.BACKGROUND;
0539: return;
0540: }
0541: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_BLOCK;
0542: return;
0543: case CBA:
0544: if (ch == EOT) {
0545: parseState = PARSESTATE_FINISHED;
0546: return;
0547: }
0548: if (ch == '/') {
0549: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_BLOCK;
0550: parseState = COD;
0551: return;
0552: }
0553: if (ch == '*') {
0554: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_BLOCK;
0555: parseState = CBA;
0556: return;
0557: }
0558: if (isEmpty(ch)) {
0559: sourceTypes[parseTypePos++] = JavaSourceType.BACKGROUND;
0560: parseState = CB;
0561: return;
0562: }
0563: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_BLOCK;
0564: parseState = CB;
0565: return;
0566: case CJ:
0567: if (ch == EOT) {
0568: parseState = PARSESTATE_FINISHED;
0569: return;
0570: }
0571: if (ch == '*') {
0572: sourceTypes[parseTypePos++] = JavaSourceType.JAVADOC;
0573: parseState = CJA;
0574: return;
0575: }
0576: if (isEmpty(ch)) {
0577: sourceTypes[parseTypePos++] = JavaSourceType.BACKGROUND;
0578: return;
0579: }
0580: sourceTypes[parseTypePos++] = JavaSourceType.JAVADOC;
0581: return;
0582: case CJA:
0583: if (ch == EOT) {
0584: parseState = PARSESTATE_FINISHED;
0585: return;
0586: }
0587: if (ch == '/') {
0588: sourceTypes[parseTypePos++] = JavaSourceType.JAVADOC;
0589: parseState = COD;
0590: return;
0591: }
0592: if (ch == '*') {
0593: sourceTypes[parseTypePos++] = JavaSourceType.JAVADOC;
0594: parseState = CJA;
0595: return;
0596: }
0597: if (isEmpty(ch)) {
0598: sourceTypes[parseTypePos++] = JavaSourceType.BACKGROUND;
0599: parseState = CJ;
0600: return;
0601: }
0602: sourceTypes[parseTypePos++] = JavaSourceType.JAVADOC;
0603: parseState = CJ;
0604: return;
0605: case QU:
0606: if (ch == EOT) {
0607: parseState = PARSESTATE_FINISHED;
0608: return;
0609: }
0610: if (ch == '"') {
0611: sourceTypes[parseTypePos++] = JavaSourceType.STRING;
0612: parseState = COD;
0613: return;
0614: }
0615: if (ch == '\\') {
0616: parseState = QUA;
0617: return;
0618: }
0619: if (isEmpty(ch)) {
0620: sourceTypes[parseTypePos++] = JavaSourceType.BACKGROUND;
0621: return;
0622: }
0623:
0624: sourceTypes[parseTypePos++] = JavaSourceType.STRING;
0625: return;
0626: case QUA:
0627: if (ch == EOT) {
0628: sourceTypes[parseTypePos++] = JavaSourceType.STRING;
0629: parseState = PARSESTATE_FINISHED;
0630: return;
0631: }
0632: if (ch == '\\') {
0633: sourceTypes[parseTypePos++] = JavaSourceType.STRING;
0634: sourceTypes[parseTypePos++] = JavaSourceType.STRING;
0635: parseState = QU; //This one has been changed from QUA to QU in 2.0
0636: return;
0637: }
0638: if (isEmpty(ch)) {
0639: sourceTypes[parseTypePos++] = JavaSourceType.STRING;
0640: sourceTypes[parseTypePos++] = JavaSourceType.BACKGROUND;
0641: parseState = QU;
0642: return;
0643: }
0644: sourceTypes[parseTypePos++] = JavaSourceType.STRING;
0645: sourceTypes[parseTypePos++] = JavaSourceType.STRING;
0646: parseState = QU;
0647: return;
0648: case CBJ1:
0649: if (ch == EOT) {
0650: parseState = PARSESTATE_FINISHED;
0651: sourceTypes[parseTypePos++] = JavaSourceType.UNDEFINED;
0652: sourceTypes[parseTypePos++] = JavaSourceType.UNDEFINED;
0653: return;
0654: }
0655: if (ch == '*') {
0656: parseState = CBJ2;
0657: return;
0658: }
0659: if (isEmpty(ch)) {
0660: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_BLOCK;
0661: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_BLOCK;
0662: sourceTypes[parseTypePos++] = JavaSourceType.BACKGROUND;
0663: parseState = CB;
0664: return;
0665: }
0666: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_BLOCK;
0667: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_BLOCK;
0668: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_BLOCK;
0669: parseState = CB;
0670: return;
0671: case CBJ2:
0672: if (ch == EOT) {
0673: parseState = PARSESTATE_FINISHED;
0674: sourceTypes[parseTypePos++] = JavaSourceType.UNDEFINED;
0675: sourceTypes[parseTypePos++] = JavaSourceType.UNDEFINED;
0676: sourceTypes[parseTypePos++] = JavaSourceType.UNDEFINED;
0677: return;
0678: }
0679: if (ch == '/') {
0680: parseState = COD;
0681: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_BLOCK;
0682: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_BLOCK;
0683: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_BLOCK;
0684: sourceTypes[parseTypePos++] = JavaSourceType.COMMENT_BLOCK;
0685: return;
0686: }
0687: if (isEmpty(ch)) {
0688: sourceTypes[parseTypePos++] = JavaSourceType.JAVADOC;
0689: sourceTypes[parseTypePos++] = JavaSourceType.JAVADOC;
0690: sourceTypes[parseTypePos++] = JavaSourceType.JAVADOC;
0691: sourceTypes[parseTypePos++] = JavaSourceType.BACKGROUND;
0692: parseState = CJ;
0693: return;
0694: }
0695: sourceTypes[parseTypePos++] = JavaSourceType.JAVADOC;
0696: sourceTypes[parseTypePos++] = JavaSourceType.JAVADOC;
0697: sourceTypes[parseTypePos++] = JavaSourceType.JAVADOC;
0698: sourceTypes[parseTypePos++] = JavaSourceType.JAVADOC;
0699: parseState = CJ;
0700: return;
0701: case CH1:
0702: if (ch == EOT) {
0703: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0704: parseState = PARSESTATE_FINISHED;
0705: return;
0706: }
0707: if (ch == '\\') {
0708: parseState = CH3;
0709: return;
0710: }
0711: parseState = CH2;
0712: return;
0713: case CH2:
0714: if (ch == EOT) {
0715: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0716: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0717: parseState = PARSESTATE_FINISHED;
0718: return;
0719: }
0720: if (ch == '\'') {
0721: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0722: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0723: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0724: parseState = COD;
0725: return;
0726: }
0727: sourceTypes[parseTypePos++] = JavaSourceType.UNDEFINED;
0728: sourceTypes[parseTypePos++] = JavaSourceType.UNDEFINED;
0729: sourceTypes[parseTypePos++] = JavaSourceType.UNDEFINED;
0730: parseState = COD;
0731: return;
0732: case CH3:
0733: if (ch == EOT) {
0734: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0735: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0736: parseState = PARSESTATE_FINISHED;
0737: return;
0738: }
0739: if (ch == 'u') {
0740: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0741: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0742: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0743: parseState = CH5;
0744: return;
0745: }
0746: if (ch >= '1' && ch <= '9') {
0747: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0748: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0749: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0750: parseState = CH6;
0751: return;
0752: }
0753: parseState = CH4;
0754: return;
0755: case CH4:
0756: if (ch == EOT) {
0757: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0758: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0759: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0760: parseState = PARSESTATE_FINISHED;
0761: return;
0762: }
0763: if (ch == '\'') {
0764: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0765: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0766: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0767: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0768: parseState = COD;
0769: return;
0770: }
0771: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0772: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0773: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0774: sourceTypes[parseTypePos++] = JavaSourceType.CODE;
0775: parseState = COD;
0776: return;
0777: case CH6:
0778: if (ch == EOT) {
0779: parseState = PARSESTATE_FINISHED;
0780: return;
0781: }
0782: if (ch == '\'') {
0783: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0784: parseState = COD;
0785: return;
0786: }
0787: if (ch >= '0' && ch <= '9') {
0788: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0789: return;
0790: }
0791: sourceTypes[parseTypePos++] = JavaSourceType.UNDEFINED;
0792: parseState = COD;
0793: return;
0794: case CH5:
0795: if (ch == EOT) {
0796: parseState = PARSESTATE_FINISHED;
0797: return;
0798: }
0799: if (ch == '\'') {
0800: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0801: parseState = COD;
0802: return;
0803: }
0804: if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f')
0805: || (ch >= 'A' && ch <= 'F')) {
0806: sourceTypes[parseTypePos++] = JavaSourceType.CHAR_CONSTANT;
0807: return;
0808: }
0809: sourceTypes[parseTypePos++] = JavaSourceType.UNDEFINED;
0810: parseState = COD;
0811: return;
0812: }
0813: }
0814:
0815: /**
0816: * Second step for parsing. The categories from the first step are further
0817: * divided: COMMENT_JAVADOC to COMMENT_JAVADOC and COMMENT_KEYWORD CODE to
0818: * CODE, CODE_TYPE and CODE_KEYWORD
0819: */
0820: private void parseTwo() {
0821: for (int index = 0; index < sourceTypes.length; ++index) {
0822: if (sourceTypes[index] == JavaSourceType.CODE) {
0823: if (isParenthesis(sourceCode.charAt(index))) {
0824: mark(index, JavaSourceType.PARENTHESIS);
0825: }
0826: }
0827: }
0828:
0829: int start = 0;
0830: int end = 0;
0831:
0832: while (end < sourceTypes.length - 1) {
0833: while (end < sourceTypes.length - 1
0834: && sourceTypes[end + 1] == sourceTypes[start])
0835: ++end;
0836:
0837: parseTwo(start, end);
0838:
0839: start = end + 1;
0840: end = start;
0841: }
0842: }
0843:
0844: private boolean isParenthesis(char ch) {
0845: return ch == '{' || ch == '}' || ch == '[' || ch == ']'
0846: || ch == '(' || ch == ')';
0847: }
0848:
0849: private void parseTwo(int start, int end) {
0850: if (sourceTypes[start] == JavaSourceType.JAVADOC) {
0851: parseTwoCommentBlock(start, end);
0852: return;
0853: } else if (sourceTypes[start] == JavaSourceType.CODE) {
0854: parseTwoCode(start, end);
0855: return;
0856: }
0857:
0858: //Keine weitere Unterteilung möglich
0859: return;
0860: }
0861:
0862: /**
0863: * Looks for primitive datatyes and keywords in the given region.
0864: */
0865: private void parseTwoCode(int start, int end) {
0866: String code = sourceCode.substring(start, end + 1);
0867:
0868: int index = start;
0869: StringTokenizer st = new StringTokenizer(code, DELIMITERS, true);
0870: while (st.hasMoreTokens()) {
0871: String s = st.nextToken();
0872: // if (s.length()==1){
0873: // char ch=s.charAt(0);
0874: // if (ch=='{' || ch=='}' ||
0875: // ch=='[' || ch==']' ||
0876: // ch=='(' || ch==')'){
0877: // mark(index, JavaSourceType.PARENTHESIS);
0878: // }
0879: // ++index;
0880: // }else{
0881: //Keyword?
0882: if (tableJavaKeywords.containsKey(s)) {
0883: mark(index, index + s.length(), JavaSourceType.KEYWORD);
0884: if (s.equals("package")) {
0885: int i1 = sourceCode.indexOf(';', index + 1);
0886: if (i1 != -1) {
0887: source.getStatistic().setPackageName(
0888: sourceCode.substring(
0889: index + s.length(), i1).trim());
0890: }
0891: }
0892: } else {
0893: //Datatype?
0894: for (int i = 0; i < PRIMITIVE_DATATYPES.length; ++i) {
0895: if (s.equals(PRIMITIVE_DATATYPES[i])) {
0896: mark(index, index + s.length(),
0897: JavaSourceType.CODE_TYPE);
0898: break;
0899: }
0900: }
0901: }
0902: index += s.length();
0903: // }
0904: }
0905: }
0906:
0907: /**
0908: * Tries to find JavaDoc comment keywords and html tags @l
0909: */
0910: private void parseTwoCommentBlock(int start, int end) {
0911: int i1 = indexOf('@', sourceCode, start, end);
0912:
0913: while (i1 != -1 && i1 + 1 < end) {
0914: int i2 = i1 + 1;
0915:
0916: char ch = sourceCode.charAt(i2 + 1);
0917: while (i2 < end
0918: && ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) {
0919: ch = sourceCode.charAt(++i2 + 1);
0920: }
0921:
0922: String s = sourceCode.substring(i1, i2 + 1);
0923: //s is likely to be a valid JavaDoc-Tag
0924:
0925: // if ((s.equals("@link") || s.equals("@linkplain"))
0926: // && sourceCode.charAt(i1 - 1) == '{'
0927: // && start > 0) {
0928: // mark(i1 - 1, i1 + 5, JavaSourceType.JAVADOC_LINKS);
0929: // }
0930: // else
0931: if (tableJavaDocKeywords.containsKey(s)) {
0932: mark(i1, i2 + 1, JavaSourceType.JAVADOC_KEYWORD);
0933: }
0934:
0935: i1 = indexOf('@', sourceCode, i2, end);
0936: }
0937:
0938: //find html tags
0939: i1 = indexOf('<', sourceCode, start, end);
0940: while (i1 != -1 && i1 + 1 < end) {
0941: int i2 = sourceCode.indexOf('>', i1 + 1);
0942:
0943: // char ch=sourceCode.charAt(i2+1);
0944: // while(i2<end && ch!='>'){
0945: // ch=sourceCode.charAt(++i2+1);
0946: // }
0947: if (i2 == -1) {
0948: i1 = -1;
0949: break;
0950: }
0951: if (hasTypeOrEmpty(sourceTypes, i1, i2 + 1,
0952: JavaSourceType.JAVADOC)) {
0953: mark(i1, i2 + 1, JavaSourceType.JAVADOC_HTML_TAG);
0954: }
0955: i1 = indexOf('<', sourceCode, i2, end);
0956: }
0957: }
0958:
0959: private static boolean hasTypeOrEmpty(JavaSourceType[] sourceTypes,
0960: int startIndex, int endIndex, JavaSourceType javaSourceType) {
0961:
0962: for (int i = startIndex; i <= endIndex; ++i) {
0963: if (!sourceTypes[i].equals(javaSourceType)
0964: && !sourceTypes[i]
0965: .equals(JavaSourceType.BACKGROUND)) {
0966: return false;
0967: }
0968: }
0969: return true;
0970: }
0971:
0972: /**
0973: * Third step for parsing: Finding number constants. CODE is further divided
0974: * to CODE and NUM_CONSTANT
0975: */
0976: private void parseThree() {
0977: int start = 0;
0978: int end = 0;
0979:
0980: while (end < sourceTypes.length - 1) {
0981: while (end < sourceTypes.length - 1
0982: && sourceTypes[end + 1] == sourceTypes[start]) {
0983: ++end;
0984: }
0985:
0986: if (sourceTypes[start] == JavaSourceType.CODE) {
0987: parseThree(start, end);
0988: }
0989:
0990: start = end + 1;
0991: end = start;
0992: }
0993:
0994: expandJavaDocLinks();
0995: }
0996:
0997: private void expandJavaDocLinks() {
0998: expandEmbracedJavaDocTag("@link", JavaSourceType.JAVADOC_LINKS);
0999: expandEmbracedJavaDocTag("@linkplain",
1000: JavaSourceType.JAVADOC_LINKS);
1001: }
1002:
1003: private void expandEmbracedJavaDocTag(String tag,
1004: JavaSourceType type) {
1005: String pattern = "{" + tag;
1006:
1007: for (int index = 0; index < sourceTypes.length; ++index) {
1008: int start = sourceCode.indexOf(pattern, index);
1009: if (start == -1) {
1010: break;
1011: }
1012:
1013: char ch = sourceCode.charAt(start + pattern.length());
1014: if (Character.isLetterOrDigit(ch)) {
1015: break;
1016: }
1017:
1018: if (!checkRegion(start + 1, start + 1 + tag.length() - 1,
1019: new IJavaSourceTypeChecker() {
1020: public boolean isValid(JavaSourceType type) {
1021: return type
1022: .equals(JavaSourceType.JAVADOC_KEYWORD);
1023: }
1024: })) {
1025: break;
1026: }
1027:
1028: int end = sourceCode.indexOf('}', start + pattern.length());
1029: if (end == -1) {
1030: break;
1031: }
1032:
1033: //Check region, can only be JavaDoc and Background
1034: if (checkRegion(start + 1 + tag.length(), end,
1035: new IJavaSourceTypeChecker() {
1036: public boolean isValid(JavaSourceType type) {
1037: return type
1038: .equals(JavaSourceType.BACKGROUND)
1039: || type
1040: .equals(JavaSourceType.JAVADOC);
1041: }
1042: })) {
1043: markWithoutBackground(start, end, type);
1044: }
1045: index = end;
1046: }
1047:
1048: }
1049:
1050: private boolean checkRegion(int start, int end,
1051: IJavaSourceTypeChecker checker) {
1052: for (int i = start; i <= end; ++i) {
1053: if (!checker.isValid(sourceTypes[i])) {
1054: return false;
1055: }
1056: }
1057: return true;
1058: }
1059:
1060: private void markWithoutBackground(int start, int end,
1061: JavaSourceType type) {
1062: for (int i = start; i <= end; ++i) {
1063: if (!sourceTypes[i].equals(JavaSourceType.BACKGROUND)) {
1064: sourceTypes[i] = type;
1065: }
1066: }
1067: }
1068:
1069: /**
1070: * Looks for number constants (NUM_CONSTANT) in the selected region.
1071: */
1072: private void parseThree(int start, int end) {
1073: parseState = PARSESTATE_START;
1074: parseSourcePos = start;
1075: parseTypePos = start - 1;
1076: counter = 0;
1077:
1078: while (parseState != PARSESTATE_FINISHED) {
1079: parseThreeDo(end);
1080: }
1081: }
1082:
1083: /**
1084: * State-machine for NUM_CONSTANTs
1085: */
1086: private void parseThreeDo(int end) {
1087: char ch = EOT;
1088:
1089: if (parseSourcePos <= end)
1090: ch = sourceCode.charAt(parseSourcePos);
1091:
1092: ++parseSourcePos;
1093: ++parseTypePos;
1094:
1095: switch (parseState) {
1096: case PARSESTATE_START:
1097: if (ch == EOT) {
1098: parseState = PARSESTATE_FINISHED;
1099: return;
1100: }
1101: if (ch == '.') {
1102: ++counter;
1103: parseState = PARSESTATE_DA;
1104: return;
1105: }
1106: if (ch == '0') {
1107: ++counter;
1108: parseState = PARSESTATE_HIA;
1109: return;
1110: }
1111: if (ch >= '1' && ch <= '9') {
1112: ++counter;
1113: parseState = PARSESTATE_NA;
1114: return;
1115: }
1116: if (isNumberDelimiter(ch)) {
1117: //stay in this parse state
1118: return;
1119: }
1120: parseState = PARSESTATE_NEUTRAL;
1121: return;
1122: case PARSESTATE_NEUTRAL:
1123: if (ch == EOT) {
1124: parseState = PARSESTATE_FINISHED;
1125: return;
1126: }
1127: if (isNumberDelimiter(ch)) {
1128: parseState = PARSESTATE_START;
1129: return;
1130: }
1131: return;
1132: case PARSESTATE_DA:
1133: if (ch == EOT) {
1134: parseState = PARSESTATE_FINISHED;
1135: return;
1136: }
1137: if (ch >= '0' && ch <= '9') {
1138: ++counter;
1139: parseState = PARSESTATE_NA;
1140: return;
1141: }
1142: if (isNumberDelimiter(ch)) {
1143: parseState = PARSESTATE_START;
1144: counter = 0;
1145: return;
1146: }
1147: parseState = PARSESTATE_NEUTRAL;
1148: counter = 0;
1149: return;
1150: case PARSESTATE_NA:
1151: if (ch == EOT) {
1152: parseState = PARSESTATE_FINISHED;
1153: mark(parseTypePos - counter, parseTypePos,
1154: JavaSourceType.NUM_CONSTANT);
1155: return;
1156: }
1157: if (ch == '.' || (ch >= '0' && ch <= '9')) {
1158: ++counter;
1159: return;
1160: }
1161: if (ch == 'e') {
1162: parseState = PARSESTATE_EXP;
1163: ++counter;
1164: return;
1165: }
1166: if (ch == 'f' || ch == 'F' || ch == 'd' || ch == 'D'
1167: || ch == 'l' || ch == 'L') {
1168: ++counter;
1169: mark(parseTypePos - counter + 1, parseTypePos + 1,
1170: JavaSourceType.NUM_CONSTANT);
1171: parseState = PARSESTATE_NEUTRAL;
1172: counter = 0;
1173: return;
1174: }
1175: if (isNumberDelimiter(ch)) {
1176: parseState = PARSESTATE_START;
1177: mark(parseTypePos - counter, parseTypePos,
1178: JavaSourceType.NUM_CONSTANT);
1179: counter = 0;
1180: return;
1181: }
1182: mark(parseTypePos - counter, parseTypePos,
1183: JavaSourceType.NUM_CONSTANT);
1184: parseState = PARSESTATE_NEUTRAL;
1185: counter = 0;
1186: return;
1187: case PARSESTATE_HIA:
1188: if (ch == EOT) {
1189: parseState = PARSESTATE_FINISHED;
1190: mark(parseTypePos - counter, parseTypePos,
1191: JavaSourceType.NUM_CONSTANT);
1192: return;
1193: }
1194: if (ch == 'x' || ch == 'X') {
1195: parseState = PARSESTATE_HEX;
1196: ++counter;
1197: return;
1198: }
1199: if (ch == '.' || (ch >= '0' && ch <= '9')) {
1200: ++counter;
1201: parseState = PARSESTATE_NA;
1202: return;
1203: }
1204: if (ch == 'f' || ch == 'F' || ch == 'd' || ch == 'D'
1205: || ch == 'l' || ch == 'L') {
1206: ++counter;
1207: mark(parseTypePos - counter + 1, parseTypePos + 1,
1208: JavaSourceType.NUM_CONSTANT);
1209: parseState = PARSESTATE_NEUTRAL;
1210: counter = 0;
1211: return;
1212: }
1213: if (isNumberDelimiter(ch)) {
1214: parseState = PARSESTATE_START;
1215: mark(parseTypePos - counter, parseTypePos,
1216: JavaSourceType.NUM_CONSTANT);
1217: counter = 0;
1218: return;
1219: }
1220: mark(parseTypePos - counter, parseTypePos,
1221: JavaSourceType.NUM_CONSTANT);
1222: parseState = PARSESTATE_NEUTRAL;
1223: counter = 0;
1224: return;
1225: case PARSESTATE_HEX:
1226: if (ch == EOT) {
1227: parseState = PARSESTATE_FINISHED;
1228: mark(parseTypePos - counter, parseTypePos,
1229: JavaSourceType.NUM_CONSTANT);
1230: return;
1231: }
1232: if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f')
1233: || (ch >= 'A' && ch <= 'F')) {
1234: ++counter;
1235: parseState = PARSESTATE_HEX;
1236: return;
1237: }
1238: if (ch == 'l' || ch == 'L') {
1239: ++counter;
1240: mark(parseTypePos - counter + 1, parseTypePos + 1,
1241: JavaSourceType.NUM_CONSTANT);
1242: parseState = PARSESTATE_NEUTRAL;
1243: counter = 0;
1244: return;
1245: }
1246: if (isNumberDelimiter(ch)) {
1247: parseState = PARSESTATE_START;
1248: mark(parseTypePos - counter, parseTypePos,
1249: JavaSourceType.NUM_CONSTANT);
1250: counter = 0;
1251: return;
1252: }
1253: mark(parseTypePos - counter, parseTypePos,
1254: JavaSourceType.NUM_CONSTANT);
1255: parseState = PARSESTATE_NEUTRAL;
1256: counter = 0;
1257: return;
1258: case PARSESTATE_EXP:
1259: if (ch == EOT) {
1260: parseState = PARSESTATE_FINISHED;
1261: mark(parseTypePos - counter, parseTypePos - 1,
1262: JavaSourceType.NUM_CONSTANT);
1263: return;
1264: }
1265: if ((ch >= '0' && ch <= '9') || ch == '+' || ch == '-') {
1266: ++counter;
1267: parseState = PARSESTATE_NA;
1268: return;
1269: }
1270: if (isNumberDelimiter(ch)) {
1271: parseState = PARSESTATE_START;
1272: mark(parseTypePos - counter, parseTypePos - 1,
1273: JavaSourceType.NUM_CONSTANT);
1274: counter = 0;
1275: return;
1276: }
1277: mark(parseTypePos - counter, parseTypePos - 1,
1278: JavaSourceType.NUM_CONSTANT);
1279: parseState = PARSESTATE_NEUTRAL;
1280: counter = 0;
1281: return;
1282: }
1283: }
1284:
1285: /**
1286: * Marks the specified region int the source code to the given type.
1287: */
1288: private void mark(int start, int endPlusOne, JavaSourceType type) {
1289: for (int i = start; i < endPlusOne; ++i) {
1290: sourceTypes[i] = type;
1291: }
1292: }
1293:
1294: /**
1295: * Marks the character at the specified index to the given type.
1296: */
1297: private void mark(int index, JavaSourceType type) {
1298: sourceTypes[index] = type;
1299: }
1300:
1301: //public static void main(String args[]){
1302: // JavaSource j=new JavaSource(new java.io.File("JavaSourceParser.java"));
1303: //
1304: // JavaSourceParser parser=new JavaSourceParser(j);
1305: //
1306: // parser.sourceCode=parser.source.getCode();
1307: // parser.sourceTypes=new byte[parser.sourceCode.length()];
1308: //
1309: // long time0=System.currentTimeMillis();
1310: // parser.parseOne();
1311: // long time1=System.currentTimeMillis();
1312: // parser.parseTwo();
1313: // long time2=System.currentTimeMillis();
1314: // parser.parseThree();
1315: // long time3=System.currentTimeMillis();
1316: //
1317: // System.out.println("Parse1: "+(time1-time0)+"ms");
1318: // System.out.println(" 2: "+(time2-time1)+"ms");
1319: // System.out.println(" 3: "+(time3-time2)+"ms");
1320: //}
1321:
1322: }
|