0001: package tide.utils;
0002:
0003: import tide.editor.MainEditorFrame;
0004: import snow.texteditor.*;
0005: import java.util.regex.*;
0006: import java.util.*;
0007: import snow.utils.StringUtils;
0008:
0009: /** To fetch syntax info directly from source string representation.
0010: * Quicker than javaCC, used for edit and completion purposes.
0011: * Other advantage: parses locally, also when not globally valid.
0012: */
0013: public class SyntaxUtils {
0014: /** @return 0 if the pos is on the first line.
0015: * Just count the return backslash + n, ignore CRs
0016: */
0017: public static int countLinesUpToPosition(String txt, int pos) {
0018: int count = 0;
0019: for (int i = 0; i < pos; i++) {
0020: if (txt.charAt(i) == '\n')
0021: count++;
0022: }
0023: return count;
0024: }
0025:
0026: /** @return true if on the same line, an uneven number of " is present
0027: TODO: odd slashes \ befor " invalidate them
0028: TODO: also look for '
0029: */
0030: public static boolean isInString(String txt, int posItem) {
0031: // look backward on the same line
0032: int pos = posItem - 1;
0033: int numberOfA = 0; // A="
0034: while (pos >= 0) {
0035: char ci = txt.charAt(pos);
0036: if (ci == '"')
0037: numberOfA++;
0038: if (ci == '\n' || ci == '\r')
0039: break;
0040: pos--;
0041: }
0042:
0043: return (numberOfA & 1) != 0;
0044: }
0045:
0046: /** in a comment iff preceded by some // on the same line
0047: * // TODO: ignore " that are in literals !
0048: */
0049: public static boolean isInComment(String txt, int posItem) {
0050: // look backward on the same line
0051: int pos = posItem - 1;
0052: boolean sameLine = true;
0053: //boolean foundEndComment = false;
0054: //boolean foundStartComment = false;
0055: while (pos >= 0) {
0056: char ci = txt.charAt(pos);
0057: if (ci == '/') {
0058: if (txt.charAt(pos + 1) == '*') {
0059: //foundStartComment = true;
0060: return true;
0061: }
0062:
0063: if (pos == 0)
0064: return false;
0065: if (sameLine && txt.charAt(pos - 1) == '/') {
0066: // TODO: ignore if // is in in litterals ! "//"
0067: return true;
0068: }
0069:
0070: if (txt.charAt(pos - 1) == '*') // we have */
0071: {
0072: //foundEndComment = true;
0073: // we're just after an ending multiline comment => sure that we're not in a comment !
0074: // even if // preceeds on the same line
0075: return false;
0076: }
0077:
0078: } else if (ci == '\n') {
0079: sameLine = false;
0080: }
0081: pos--;
0082: }
0083:
0084: return false;
0085: }
0086:
0087: /** Example: snow.utils.gui.FileChooserFilter$SearchHit => snow.utils.gui
0088: */
0089: public static String getPackageName(String jn) {
0090: int posPt = jn.lastIndexOf('.');
0091: if (posPt < 0)
0092: return jn;
0093: return jn.substring(0, posPt);
0094: }
0095:
0096: /** Given an argument string, like "123, var, nd(1), 2",
0097: * @return the number of the argument (0 for the first). 4 in this case
0098: */
0099: public static int getArgumentNumberAtEnd(String as) {
0100: int parenthesisDepth = 0;
0101: int argCount = 0;
0102:
0103: boolean inComment1 = false; // '
0104: boolean inComment2 = false; // "
0105:
0106: cl: for (int i = 0; i < as.length(); i++) {
0107: char ci = as.charAt(i);
0108: if (inComment1) {
0109: if (ci == '\'')
0110: inComment1 = false;
0111: continue cl;
0112: }
0113:
0114: if (inComment2) {
0115: if (ci == '\"')
0116: inComment2 = false;
0117: continue cl;
0118: }
0119:
0120: if (ci == '\'') {
0121: inComment1 = true;
0122: continue cl;
0123: }
0124: if (ci == '\"') {
0125: inComment2 = true;
0126: continue cl;
0127: }
0128:
0129: if (ci == '(' && i > 0)
0130: parenthesisDepth++;
0131: if (ci == ')')
0132: parenthesisDepth--;
0133:
0134: if (ci == ',' && parenthesisDepth == 0) {
0135: argCount++;
0136: }
0137:
0138: }
0139: //System.out.println("Args at end of \""+as+"\" is "+argCount);
0140: return argCount;
0141: }
0142:
0143: /** Takes greedilly a full java identifier before the position given.
0144: *
0145: Used from document filter when "." is pressed somewhere for completion purposes.
0146: Quick and not requiring a compilable state...
0147:
0148: xxx. yyy.zzz => "xxx.yyy.zzz";
0149: xxx. yy y.zzz => "y.zzz";
0150:
0151: xxx.yyy(12.32, z())[8].zzz => xxx.yyy()[].zzz
0152:
0153: casts are reduced
0154: ((Double) xxx).doubleValue() => {C:Double}.doubleValue()
0155:
0156: type variable also
0157: Vector<Double> => Vector{TV:Double}
0158:
0159:
0160: Don't look at Character.isJavaIdentifierStart() limitations.
0161: Not very exact, somehow tolerant...
0162:
0163: @return the identifier, without spaces.
0164:
0165: TODO2: count args at level -1 of parenthesis (number of ",") , allow [ ] in args
0166: April2007: skip (ignore( the type params as in Vector<Object>() => Vector();
0167: May2007: improved
0168: @param prepend "this." in the case of CTRL+space completion
0169: */
0170: public static ParsedID getJavaIdentifierBefore(SimpleDocument doc,
0171: final int pos, String prepend) {
0172: StringBuilder completeId = new StringBuilder();
0173: StringBuilder id = new StringBuilder();
0174:
0175: boolean isPreviousWhite = false;
0176: boolean isPreviousNonWhitePoint = false;
0177: int isInParenthesisDepth = 0;
0178: int isInBracketDepth = 0;
0179: int isInTypeParamDepth = 0;
0180: boolean isInLitteral1 = false;
0181: boolean isInLitteral2 = false;
0182:
0183: int lastInsertPos = pos - 1;
0184:
0185: // reverse parsing from pos-1 (the last char to consider)
0186: int i = pos - 1;
0187:
0188: el: for (; i >= 0; i--) {
0189: final char ci = doc.getCharAt(i);
0190: completeId.insert(0, ci);
0191: boolean refuseThisChar = false;
0192:
0193: if (ci == '\n') {
0194: // special whitespace: te line we are going to analyse from the end may contain a comment that must be skipped
0195: // we jump to the previous line
0196: String line = DocumentUtils.getTextOfLineAtPosition(
0197: doc, i);
0198: //System.out.println("Previous Line: "+line);
0199: int posComment = line.indexOf("//");
0200: if (posComment >= 0) {
0201: //System.out.println("Contains a comment => skipping it !");
0202: // => continue reading from that pos
0203: i = DocumentUtils.getDocPositionFor(doc,
0204: DocumentUtils.getLineNumber(doc, i),
0205: posComment);
0206: }
0207:
0208: isPreviousWhite = true;
0209: continue el; // never added
0210:
0211: } else if (Character.isWhitespace(ci)) {
0212: if (!isInLitteral1 && !isInLitteral2) // [May2007]
0213: {
0214: isPreviousWhite = true;
0215: }
0216: continue el; // never added
0217: } else if (ci == '.') {
0218: isPreviousWhite = false;
0219: if (isPreviousNonWhitePoint) {
0220: //System.out.println("stop3, two points found");
0221: break el;
0222: }
0223: isPreviousNonWhitePoint = true;
0224: } else if (ci == ',') // [Aug2006] delimiter for args => keep
0225: {
0226: isPreviousWhite = false;
0227: if (isInParenthesisDepth == 0
0228: && isInTypeParamDepth == 0) {
0229: //System.out.println("stop5");
0230: break el;
0231: }
0232: } else if (ci == '\'') // TODO: better
0233: {
0234: isPreviousWhite = false;
0235: if (isInParenthesisDepth == 0
0236: && isInTypeParamDepth == 0)
0237: break el; // stop !
0238:
0239: isInLitteral2 = !isInLitteral2;
0240: refuseThisChar = true;
0241: } else if (ci == '"') {
0242: isPreviousWhite = false;
0243: if (isInParenthesisDepth == 0
0244: && isInTypeParamDepth == 0) {
0245: break el; // stop !
0246: }
0247:
0248: isInLitteral1 = !isInLitteral1;
0249: refuseThisChar = true;
0250: } else if (Character.isJavaIdentifierPart(ci)) {
0251: //System.out.println(""+ci);
0252:
0253: // only accept spaces around dots and in args
0254: if (isInParenthesisDepth == 0
0255: && isInTypeParamDepth == 0 && isPreviousWhite
0256: && !isPreviousNonWhitePoint) {
0257:
0258: //System.out.println("stop2, "+ci);
0259: break el;
0260: }
0261:
0262: isPreviousWhite = false;
0263: isPreviousNonWhitePoint = false;
0264: } else if (ci == ')') {
0265: /*String ids = completeId.substring(1).trim();
0266: if(ids.length()>0 && Character.isJavaIdentifierStart(ids.charAt(0)))
0267: {
0268: //System.out.println("In cast "+ids+", d="+isInParenthesisDepth);
0269: }*/
0270:
0271: if (isInParenthesisDepth == 0) {
0272: id.insert(0, ci); // must be inserted because ignored below
0273: lastInsertPos = i;
0274: refuseThisChar = true;
0275: }
0276: isInParenthesisDepth++;
0277: } else if (ci == '(') {
0278: isPreviousWhite = false; // [May2007]: was missing.
0279: isInParenthesisDepth--;
0280: if (isInParenthesisDepth < 0) {
0281: MainEditorFrame
0282: .debugOut("stop1, beginning ( parenthesis found");
0283: break el;
0284: }
0285: } else if (ci == ']') {
0286: isPreviousWhite = false;
0287: if (isInParenthesisDepth > 0)
0288: continue;
0289:
0290: id.insert(0, ci); // must be inserted because ignored below
0291: lastInsertPos = i;
0292: isInBracketDepth++;
0293: } else if (ci == '[') {
0294: isPreviousWhite = false;
0295: if (isInParenthesisDepth > 0)
0296: continue;
0297:
0298: isInBracketDepth--;
0299: if (isInBracketDepth < 0) {
0300: //System.out.println("stop4, beginning [ found");
0301: break el;
0302: }
0303: } else if (ci == '>') {
0304: isPreviousWhite = false;
0305: isInTypeParamDepth++;
0306: } else if (ci == '<') {
0307: isPreviousWhite = false;
0308: isInTypeParamDepth--;
0309: if (isInTypeParamDepth < 0) {
0310: //System.out.println("stop5, < found");
0311: break el;
0312: }
0313: } else if (isInBracketDepth > 0 || isInParenthesisDepth > 0
0314: || /*isInTypeParamDepth>0 ||*/
0315: isInLitteral2 || isInLitteral1) {
0316: continue el;
0317: } else {
0318: //System.out.println("stop6 "+ci);
0319: break el;
0320: }
0321:
0322: if (refuseThisChar) {
0323: //System.out.println(" refuse");
0324: continue el;
0325: }
0326:
0327: // ignore bracket contents but let parenthesis !! [April2007], they are removed below
0328: if (isInBracketDepth > 0 /*|| isInParenthesisDepth>0*/
0329: || isInLitteral2 || isInLitteral1) {
0330: continue el;
0331: }
0332:
0333: // accept it
0334: lastInsertPos = i;
0335: id.insert(0, ci);
0336: }
0337:
0338: // System.out.println("id0='"+id+"'");
0339:
0340: int startPos = lastInsertPos;
0341:
0342: // look before
0343:
0344: StringBuilder before = new StringBuilder();
0345: al: for (; i >= 0; i--) {
0346: char ci = doc.getCharAt(i);
0347: if (before.length() == 0) {
0348: //first char
0349: if (Character.isWhitespace(ci))
0350: continue al;
0351: if ("{}()[]\"'+-*/%^|&".indexOf(ci) >= 0) {
0352: // as first => add
0353: before.insert(0, ci);
0354: break al;
0355: }
0356: } else {
0357: if (Character.isWhitespace(ci))
0358: break al;
0359: if (!Character.isLetterOrDigit(ci))
0360: break al;
0361: }
0362:
0363: before.insert(0, ci);
0364: }
0365:
0366: StringBuilder after = new StringBuilder();
0367: afl: for (i = pos; i < doc.getLength(); i++) {
0368: char ci = doc.getCharAt(i);
0369: if (!Character.isWhitespace(ci)) {
0370: after.append(ci);
0371: break afl;
0372: }
0373: }
0374:
0375: // [Feb2007] if() xxx and for() and other such starts are removed, they are no casts
0376: /* int parPos = id.indexOf("()");
0377: if(parPos>0) //strict
0378: {
0379: String kw = id.substring(0,parPos).trim();
0380: if(SyntaxUtils.isKeyWord( kw ))
0381: {
0382: System.out.println("removing keyword() at beginning of "+id);
0383: //id = id.substring(parPos+2);
0384: id.replace(0,parPos+2, "");
0385:
0386: startPos += kw.length()+2; //plus the content of () ;-( ... //TODO
0387: }
0388: }
0389: else if(parPos==0)
0390: {
0391: id.replace(0,2,"");
0392: startPos += 2; //plus the content of () ;-( ... //TODO
0393: }*/
0394:
0395: // ignore '.' at beginning
0396: if (id.length() > 0 && id.charAt(0) == '.') {
0397: //ParsedID pid = new ParsedID(id.substring(1), before.toString(), after.toString(), startPos);
0398: //return pid;
0399: id.replace(0, 1, "");
0400: before.append("."); // shift startPos ??
0401: }
0402:
0403: // reduce casts ( ((Double)xxx).nono(23,43).doubleValue() => #Double.nono().doubleValue()
0404: Map<String, String> castsReplacements = replaceAllCastsWithName(id);
0405: // remove parenthesis contents (casts are already removed, remains only the args)
0406: removeAllContents(id, '(', ')');
0407:
0408: // reduce type variables
0409: Map<String, String> typeVariablesReplacements = replaceAllTypeVariablesWithName(id);
0410: removeAllContents(id, '<', '>');
0411:
0412: /* if(castsReplacements.size()>0)
0413: System.out.println("C:"+castsReplacements);
0414: if(typeVariablesReplacements.size()>0)
0415: System.out.println("TV:"+typeVariablesReplacements);
0416:
0417: System.out.println("id="+id);
0418: System.out.println("bef="+before);
0419: System.out.println("after="+after);
0420: */
0421:
0422: // [May2007]: "if()System.out.println()"
0423: falseCastMatcher.reset(id);
0424: if (falseCastMatcher.find()) {
0425: int st = falseCastMatcher.start();
0426: //System.out.println("########################### False cast found in "+id+", st="+st);
0427: //System.out.println("Removing: "+id.substring(0,st+1));
0428: before.append(id.substring(0, st + 1));
0429: id.delete(0, st + 1);
0430:
0431: // shift startPos ??
0432: }
0433:
0434: // a new special case...
0435: String ids = id.toString();
0436: if (ids.startsWith(">")) {
0437: ids = ids.substring(1);
0438: before.append(">"); // shift startPos ??
0439: }
0440:
0441: if (prepend != null) // [Feb2008]
0442: {
0443: ids = prepend + ids;
0444: }
0445:
0446: ParsedID pid = new ParsedID(ids, before.toString(), after
0447: .toString(), startPos, castsReplacements,
0448: typeVariablesReplacements);
0449: return pid;
0450: }
0451:
0452: /** @return the same passed ref for convenience.
0453: * Removes all the content between the balanced open, end items.
0454: * Useful for example to remove type params, "<", ">".
0455: */
0456: public static StringBuilder removeAllContents(
0457: final StringBuilder ids, char open, char end) {
0458: int posSt = 0;
0459: while ((posSt = ids.indexOf(String.valueOf(open), posSt)) >= 0) {
0460: int posEnd = matchingEndBrace(ids, posSt, open, end);
0461: if (posEnd < 0)
0462: return ids;
0463:
0464: ids.replace(posSt + 1, posEnd, "");
0465:
0466: posSt++;
0467: }
0468: return ids;
0469:
0470: }
0471:
0472: /** Reduce type variables Vector<String> => Vector#{TV:1}
0473: * the map contains the replacements {1,String}.
0474: */
0475: public static Map<String, String> replaceAllTypeVariablesWithName(
0476: StringBuilder ids) {
0477: Map<String, String> replacements = new HashMap<String, String>();
0478: String idt = ids.toString();
0479: int posSt = -1;
0480: while ((posSt = ids.indexOf("<", posSt + 1)) >= 0) {
0481: int posEnd = matchingEndBrace(ids, posSt, '<', '>');
0482: if (posEnd < 0)
0483: continue;
0484:
0485: int n = replacements.size() + 1;
0486: String vn = ids.substring(posSt + 1, posEnd);
0487: replacements.put("" + n, vn);
0488: ids.replace(posSt, posEnd + 1, "{TV:" + n + "}");
0489: }
0490:
0491: return replacements;
0492: }
0493:
0494: /** Reduce casts ( ((Double)xxx).nono(23,43).doubleValue() => #{C:1}.nono().doubleValue()
0495: * the map contains the replacements {1,Double}.
0496: */
0497: public static Map<String, String> replaceAllCastsWithName(
0498: StringBuilder ids) {
0499: //System.out.println("replaceAllCastsWithName in "+ids);
0500: Map<String, String> replacements = new HashMap<String, String>();
0501: String idt = ids.toString();
0502: // shared matcher !
0503: castMatcher.reset(idt);
0504: int posSt = 0;
0505: while (castMatcher.find(posSt)) {
0506: posSt = castMatcher.end(1) + 1; // search next from end
0507: //System.out.println("Cast: "+castMatcher.group(1));
0508: int beg = castMatcher.start(1);
0509: int end = castMatcher.end(1);
0510:
0511: // search previous opening (
0512: int begRep = -1;
0513: sb: for (int i = beg - 2; i >= 0; i--) // start from one before "("
0514: {
0515: if (ids.charAt(i) == '(') {
0516: begRep = i;
0517: break sb;
0518: }
0519: }
0520: if (begRep < 0)
0521: continue; // go to next
0522: // search corresponding ending )
0523: int endRep = matchingEndBrace(idt, begRep, '(', ')');
0524: if (endRep < 0)
0525: continue; // go to next
0526:
0527: //System.out.println("cast par "+idt.substring(begRep,endRep+1));
0528: int n = replacements.size() + 1;
0529: replacements.put("" + n, castMatcher.group(1));
0530: ids.replace(begRep, endRep + 1, "{C:" + n + "}");
0531:
0532: // continue from the end
0533: posSt = endRep + 1;
0534: }
0535:
0536: return replacements;
0537: }
0538:
0539: public static int matchingEndBrace(CharSequence text, int pos,
0540: char open, char end) {
0541: int depth = 1;
0542: for (int i = pos + 1; i < text.length(); i++) {
0543: if (text.charAt(i) == open)
0544: depth++;
0545: else if (text.charAt(i) == end) {
0546: depth--;
0547: if (depth == 0)
0548: return i;
0549: }
0550: }
0551: return -1;
0552:
0553: }
0554:
0555: // Used to locate casts. Because regex does not perform balancing,
0556: // this only locate the cast, the external ( and ) if any must be balanced search.
0557: // the string must be without spaces.
0558: static Matcher castMatcher = Pattern.compile(
0559: "\\(([\\w0-9_\\.<>]+?)\\)[\\w_$0-9]").matcher("");
0560:
0561: // utility to remove special constructs... HAckKKK
0562: static Matcher falseCastMatcher = Pattern.compile("\\)[^\\.${<]")
0563: .matcher(""); // NOT dot
0564:
0565: /** Not really robust, but is a minimal condition to look before trying to execute some class.
0566: */
0567: public static boolean hasStaticMainMethod(String txt) {
0568: // the declaring class may NOT NECESSARY PUBLIC !
0569: // args may be String[] or String...
0570:
0571: Matcher m = publicstaticvoidmain.matcher(txt);
0572: return m.find();
0573: }
0574:
0575: // no need to put ( at end, a void main is a method...
0576: // todo: only accept String[] or String... signatures)
0577: static final Pattern publicstaticvoidmain = Pattern
0578: .compile("public\\s+static\\s+void\\s+main");
0579:
0580: /** Used in javadoc completion.
0581: * @return [0]: the return type
0582: * @return [1]: the arguments names list (ONLY MADE)
0583: * @return [2]: the throws list
0584: * this must be called from within a comment
0585: */
0586: public static ArrayList<String>[] parseSignatureAfterForJavaDoc(
0587: String doc, int pos) {
0588: @SuppressWarnings("unchecked")
0589: ArrayList<String>[] ret = new ArrayList[3];
0590:
0591: // 1) seek comment end
0592: int posEndComment = -1;
0593: sl: for (int i = pos; i < doc.length() - 1; i++) {
0594: if (doc.charAt(i) == '*' && doc.charAt(i + 1) == '/') // < length-1 ==> can access i+1
0595: {
0596: posEndComment = i + 1;
0597: break sl;
0598: }
0599: }
0600:
0601: if (posEndComment == -1) {
0602: System.out.println("no end comment "
0603: + doc.substring(pos, pos + 20));
0604: return null;
0605: }
0606:
0607: // 2) search open parenthesis, stop if ";" encountered
0608: // TODO: scan the return type (and modifiers)
0609:
0610: int posOpenParenthesis = -1; // (
0611: sl: for (int i = posEndComment + 1; i < doc.length(); i++) {
0612: char ci = doc.charAt(i);
0613: if (ci == '(') {
0614: posOpenParenthesis = i;
0615: break sl;
0616: } else if (ci == ';')
0617: break;
0618: }
0619:
0620: if (posOpenParenthesis == -1) {
0621: System.out.println("no open parenthesis"
0622: + doc.substring(pos, pos + 20));
0623: return null;
0624: }
0625:
0626: // 3) scan for arguments
0627:
0628: // go until "{" or ";"
0629:
0630: // TODO: ignore comments
0631: int posEndParenthesis = -1; // )
0632: ArrayList<String> params = new ArrayList<String>();
0633: ret[1] = params;
0634: StringBuilder actualParam = new StringBuilder();
0635: sl: for (int i = posOpenParenthesis + 1; i < doc.length() - 1; i++) // < length-1 ==> can access i+1
0636: {
0637: char ci = doc.charAt(i);
0638: if (ci == '{')
0639: break;
0640: else if (ci == ';')
0641: break;
0642: else if (ci == '{') {
0643: } else if (ci == ')') {
0644: if (actualParam.length() > 0) {
0645: params.add(extractNameFromDecl(actualParam
0646: .toString().trim()));
0647: actualParam.setLength(0);
0648: }
0649: posEndParenthesis = i;
0650: break;
0651: } else if (ci == ',') {
0652: if (actualParam.length() > 0) {
0653: params.add(extractNameFromDecl(actualParam
0654: .toString().trim()));
0655: actualParam.setLength(0);
0656: }
0657: } else {
0658: actualParam.append(ci);
0659: }
0660: }
0661:
0662: // 4) scan the throws until "{"
0663:
0664: //System.out.println("params="+params);
0665: return ret;
0666: }
0667:
0668: /** "int a" => a
0669: */
0670: private static String extractNameFromDecl(final String decl) {
0671: int pos = decl.lastIndexOf(' ');
0672: if (pos == -1) {
0673: MainEditorFrame.debugOut("Cannot extract name from '"
0674: + decl + "'");
0675: return decl;
0676: }
0677: return decl.substring(pos + 1);
0678: }
0679:
0680: /** TEST CASE
0681: */
0682: private static void test_getJavaIdentifierBefore() {
0683: test_getJavaIdentifierBefore(
0684: "[]de(xxx(23).a$_3[7][3] . yy2y. zzz[](1, \"33[]\", abc(\"12\"), \"1/2\")",
0685: "xxx().a$_3[][].yy2y.zzz[]()");
0686: test_getJavaIdentifierBefore(
0687: "((JFrame) aaa).hello(1,2, 3, aa(17, (JInternalFrame) abc))",
0688: "{C:1}.hello(1,2,3,aa(17,{C:2}");
0689: test_getJavaIdentifierBefore(
0690: " javax.\n//commentedline xxx.\r\n swing.JFrame.EXIT_ON_CLOSE",
0691: "javax.swing.JFrame.EXIT_ON_CLOSE");
0692: test_getJavaIdentifierBefore("a(1).b(2).c().d()",
0693: "a().b().c().d()");
0694: test_getJavaIdentifierBefore("new Vector<String", "String");
0695: test_getJavaIdentifierBefore("new Vector<String>()",
0696: "Vector{TV:1}()");
0697: test_getJavaIdentifierBefore("((Double) xxx).doubleValue()",
0698: "{C:1}.doubleValue()");
0699: test_getJavaIdentifierBefore(
0700: "((Double<Int>) xxx).nono(23,43).doubleValue(<23>)<String>",
0701: "{C:1}.nono().doubleValue(){TV:1}");
0702: test_getJavaIdentifierBefore("if(true){ System.out.println()",
0703: "System.out.println()");
0704: test_getJavaIdentifierBefore("if(true) System.out.println()",
0705: "System.out.println()");
0706: test_getJavaIdentifierBefore("System.out.println(\"ohlala\")",
0707: "System.out.println()");
0708: test_getJavaIdentifierBefore("System.out.println(\" \")",
0709: "System.out.println()");
0710: test_getJavaIdentifierBefore("println(1,2)", "println()");
0711: test_getJavaIdentifierBefore("println( 1, 2 )", "println()");
0712: test_getJavaIdentifierBefore(">println( 1, 2 )", "println()");
0713:
0714: }
0715:
0716: private static void test_getJavaIdentifierBefore(String id,
0717: String res) {
0718: System.out.println("\ntesting <<" + id + ">>");
0719: SimpleDocument doc = new SimpleDocument();
0720: doc.append(id);
0721:
0722: ParsedID pid = getJavaIdentifierBefore(doc, doc.getLength(),
0723: null);
0724: if (!pid.identifierChain.equals(res)) {
0725: System.out.println("##### ERROR: " + pid.identifierChain);
0726: throw new RuntimeException();
0727: //new Throwable().printStackTrace();
0728: } else {
0729: System.out.println("\t\t ok: " + pid.identifierChain);
0730: }
0731: }
0732:
0733: /** a.b.d is valid, 12.43 not.
0734: * keywords are not allowed, names starting with bad letter also not.
0735: * (JFrame) as simplified cast form of ((JFrame) aa) is allowed
0736: */
0737: public static boolean isValidIdentifier(String id) {
0738: if (id.length() == 0)
0739: return false;
0740: // only this, super and class keywords are accepted as parts of expressions (e.g. void.class is valid !)
0741: if (id.equals("this") || id.equals("super")
0742: || id.equals("class") || id.equals("void"))
0743: return true;
0744: // other keywords are not accepted.
0745: if (SyntaxUtils.isKeyWord(id))
0746: return false;
0747: if (!Character.isJavaIdentifierStart(id.charAt(0)))
0748: return false;
0749: // todo: allow only "." and " " around points and javaIdPart
0750: // no two points, ...
0751:
0752: return true;
0753: }
0754:
0755: /** @return {name, params} for example {String, null} as in "String" or {Vector, String} as in Vector<String>.
0756: * or {Class[], ?} as in "Class<?>[]"
0757: */
0758: public static String[] splitTypeParams(String tp) {
0759: if (tp == null)
0760: return null;
0761:
0762: int start = tp.indexOf('<');
0763: if (start == -1)
0764: return new String[] { tp, null };
0765: int end = tp.lastIndexOf('>');
0766: if (end == -1)
0767: return new String[] { tp, null };
0768:
0769: // [March2008] was false for Class<?>[], forgetting the array part !
0770:
0771: return new String[] {
0772: tp.substring(0, start) + tp.substring(end + 1),
0773: tp.substring(start + 1, end) };
0774:
0775: }
0776:
0777: /** Removes all array [] elements at the end, including content
0778: * internal: remove everything after the first "[" occurence.
0779: */
0780: public static String removeAllArrayPartsAtEnd(String id) {
0781: int pos = id.indexOf('[');
0782: if (pos < 0)
0783: return id;
0784: return id.substring(0, pos);
0785: }
0786:
0787: /** Removes the last array [ ] element at the end, including content
0788: */
0789: public static String removeLastArrayPartAtEnd(String id) {
0790: int pos = id.lastIndexOf('[');
0791: if (pos < 0)
0792: return id;
0793: return id.substring(0, pos);
0794: }
0795:
0796: /** Map<Integer, List<A>> => Map.
0797: * the < and > must be balanced... uses first and lastindexof
0798: */
0799: public static String removeSingleTypeParameters(String id) {
0800: int start = id.indexOf('<');
0801: if (start < 0)
0802: return id;
0803: int end = id.lastIndexOf('>');
0804: if (end < 0)
0805: return id;
0806: return id.substring(0, start) + id.substring(end + 1);
0807: }
0808:
0809: public static String getJavaNameSimpleIfLongForView(String jn) {
0810: if (jn.length() < 30)
0811: return jn;
0812: return makeSingleJavaNameSimple(jn);
0813: }
0814:
0815: /** Suitable for a single name, like java.lang.Object => Object.
0816: */
0817: public static String makeSingleJavaNameSimple(String jn) {
0818: // [July2007]: used for abstract text generation, was failing for arrays.
0819: if (jn.endsWith(";")) {
0820: jn = convertVmNameToFullJavaNames(jn);
0821: }
0822:
0823: // remove all generics parameter stuff // [March2008]: failed for java.lang.Class<? extends java.lang.annotation.Annotation>. now works
0824: int pos1 = jn.indexOf('<');
0825: if (pos1 >= 0) {
0826: int pos2 = jn.indexOf('>', pos1);
0827: if (pos2 >= 0) {
0828: jn = jn.substring(0, pos1) + jn.substring(pos2 + 1);
0829: } else {
0830: System.out.println("??Bad syntax: " + jn);
0831: }
0832: }
0833:
0834: int posP = jn.lastIndexOf('.');
0835: if (posP < 0)
0836: return jn;
0837: return jn.substring(posP + 1);
0838: }
0839:
0840: /** java.lang.String hello(myApp.XYZ var) => String hello(XYZ var)
0841: * NOT ROBUST:
0842: * => only simplify if the guessed package name is lowercase.
0843: * => static method calls like String.hello remains unchanged
0844: */
0845: public static String makeAllJavaNamesSimpleInText(String signature) {
0846: List<Integer> starts = new ArrayList<Integer>();
0847: List<Integer> ends = new ArrayList<Integer>();
0848: Matcher mat = javaAbsoluteIdentifierPattern.matcher(signature);
0849: int lastStart = 0;
0850:
0851: while (mat.find(lastStart)) {
0852: int start = mat.start();
0853:
0854: int end = mat.end();
0855:
0856: lastStart = end;
0857:
0858: if (Character.isUpperCase(signature.charAt(start)))
0859: continue;
0860: starts.add(start);
0861: ends.add(end);
0862: }
0863:
0864: StringBuilder ret = new StringBuilder(signature);
0865: for (int i = starts.size() - 1; i >= 0; i--) // reverse to keep indices ! (can be boosted... directly in the first loop, but i'm lazy)
0866: {
0867: String str = signature
0868: .substring(starts.get(i), ends.get(i));
0869: String rep = StringUtils.keepAfterLastExcl(str, ".");
0870: ret.replace(starts.get(i), ends.get(i), rep);
0871: }
0872:
0873: return ret.toString();
0874: }
0875:
0876: // don't start with a digit
0877: final public static Pattern javaNamePattern = Pattern
0878: .compile("[a-zA-Z_][a-zA-Z_0-9]*");
0879: // not working well: captures "tring.indexOf" !! => use javaNamePattern and filter the result using the first char instead...
0880: //final public static Pattern javaNamePatternStartsLowercase = Pattern.compile("[a-z][a-zA-Z_0-9]*");
0881:
0882: // recurse with points (at least one)
0883: final public static Pattern javaAbsoluteIdentifierPattern = Pattern
0884: .compile(javaNamePattern + "(\\s*\\.\\s*" + javaNamePattern
0885: + ")+");
0886:
0887: public static void main(String[] aa) {
0888: System.out
0889: .println(removeAllContents(new StringBuilder(
0890: "Hello<String, List<String>> aa, double bb"),
0891: '<', '>'));
0892: System.out.println("" + isInComment("123//*/abc", 7));
0893:
0894: test_getJavaIdentifierBefore();
0895: if (true)
0896: System.exit(0);
0897:
0898: System.out.println(""
0899: + convertVmNameToFullJavaNames("[Ljava/lang/String;"));
0900: System.out
0901: .println(""
0902: + Arrays
0903: .toString(extractModifiersAndType("public static final strictfp void hello")));
0904:
0905: System.out
0906: .println(""
0907: + removeSingleTypeParameters("Map<Integer, List<A>> a"));
0908: System.out.println("" + removeLastArrayPartAtEnd("abc[3][2]"));
0909: //System.out.println(makeAllJavaNamesSimple("Method checks to see if result of String.indexOf is positive, throws java.lang.Exception"));
0910: //System.out.println(makeAllJavaNamesSimple("java.lang.String hello(mypack.XYZ var) throws String.hello() "));
0911: //System.out.println(""+getArgumentNumberAtEnd("0,1,2, dd(3,3, ','),4"));
0912: System.out.println(Arrays
0913: .toString(splitTypeParams("Vector<Vector<Object>>")));
0914:
0915: /*List[] pos = parseSignatureAfterForJavaDoc (
0916: "/** Hallo * / public int[] hello(int a, double[ ] bb) {}", 5);*/
0917:
0918: System.out.println("" + convertVmNameToFullJavaNames("[[I"));
0919: System.out.println(""
0920: + convertVmNameToFullJavaNames("[Ljava.lang.Thread;"));
0921:
0922: StringBuilder s = new StringBuilder("a(1)(22)()b(3)((1,(2)))");
0923: removeAllContents(s, '(', ')');
0924: System.out.println("" + s);
0925:
0926: }
0927:
0928: public static String createVariableNameFromClassName(String cn) {
0929: if (SyntaxUtils.isKeyWord(cn)) {
0930: // int => i,double => d,...
0931: return "" + Character.toLowerCase(cn.charAt(0));
0932: }
0933: String varName = cn.replace('$', '_');
0934:
0935: if (Character.isUpperCase(varName.charAt(0))) {
0936: varName = "" + Character.toLowerCase(varName.charAt(0))
0937: + varName.substring(1);
0938: } else {
0939: varName = "_" + varName;
0940: }
0941: return varName;
0942: }
0943:
0944: /** [I => int[] (defined in Class.getName()). Names are NOT also in simple form.
0945: * Used in the decompiledClass to match javap signatures !
0946: * Also used in JMapReader and JavaDocParser.
0947: */
0948: public static String convertVmNameToFullJavaNames(
0949: final String className) {
0950: int depth = 0;
0951: int i = 0;
0952: for (; i < className.length(); i++) {
0953: if (className.charAt(i) == '[')
0954: depth++;
0955: else
0956: break;
0957: }
0958:
0959: if (depth == 0)
0960: return className;
0961:
0962: String cn = className.substring(i);
0963: StringBuilder sb = new StringBuilder();
0964:
0965: if (cn.startsWith("L")) {
0966: sb.append(cn.substring(1, cn.length() - 1)); // also remove the ";" at end
0967: } else {
0968: if (cn.length() == 1) {
0969: sb.append(getNameForVMNativeType(cn.charAt(0)));
0970: } else {
0971: sb.append("" + cn); // just a name, like java.lang.String
0972: }
0973: }
0974:
0975: // append array braces
0976: for (i = 0; i < depth; i++) {
0977: sb.append("[]");
0978: }
0979: return sb.toString();
0980: }
0981:
0982: /** I => int, ...
0983: */
0984: public static String getNameForVMNativeType(char charType) {
0985: switch (charType) {
0986: case 'I':
0987: return "int";
0988: case 'B':
0989: return "byte";
0990: case 'F':
0991: return "float";
0992: case 'Z':
0993: return "boolean";
0994: case 'S':
0995: return "short";
0996: case 'D':
0997: return "double";
0998: case 'J':
0999: return "long";
1000: case 'C':
1001: return "char";
1002: }
1003: return "?? unknown native VM type " + charType;
1004:
1005: }
1006:
1007: /** java keywords up to 1.6 */
1008: public static final TreeSet<String> javaKeywords = new TreeSet<String>(
1009: Arrays.asList(new String[] { "abstract", "boolean",
1010: "break", "byte", "case", "catch", "char", "class",
1011: "const", "continue", "default", "do", "double",
1012: "else", "extends", "final", "finally", "float",
1013: "for", "goto", "if", "implements", "import",
1014: "instanceof", "int", "interface", "long", "native",
1015: "new", "package", "private", "protected", "public",
1016: "return", "short", "static", "strictfp", "super",
1017: "switch", "synchronized", "this", "throw",
1018: "throws", "transient", "try", "void", "volatile",
1019: "while", "assert", // since 1.4
1020: "enum", // since 1.5
1021: "true", "false", "null" // not really keywords.., as null
1022: }));
1023:
1024: public static boolean isKeyWord(String w) {
1025: return javaKeywords.contains(w);
1026: }
1027:
1028: /** Also present in the javaKeywords
1029: */
1030: public static final TreeSet<String> primitiveTypes = new TreeSet<String>(
1031: Arrays
1032: .asList(new String[] { "boolean", "byte", "char",
1033: "double", "float", "int", "long", "short",
1034: "void" }));
1035:
1036: /** Also present in the javaKeywords
1037: */
1038: public static final TreeSet<String> modifiers = new TreeSet<String>(
1039: Arrays
1040: .asList(new String[] { "abstract", "final",
1041: "native", "private", "protected", "public",
1042: "static", "strictfp", "synchronized",
1043: "transient", "volatile" }));
1044:
1045: /** if one of [boolean, float, ..., void]
1046: */
1047: public static boolean isPrimitiveType(String w) {
1048: if (w == null)
1049: return false;
1050: return primitiveTypes.contains(w);
1051: }
1052:
1053: public static boolean isModifier(String w) {
1054: if (w == null)
1055: return false;
1056: return modifiers.contains(w);
1057: }
1058:
1059: /** without generics parameters.
1060: * @return {mods, type}, type being empty if none found (for constructors)
1061: * type may have several items (as in "void hello")
1062: */
1063: public static String[] extractModifiersAndType(String modAndType) {
1064: StringBuilder mods = new StringBuilder();
1065: StringBuilder type = new StringBuilder();
1066:
1067: //String type = "";
1068: for (String mti : modAndType.split("\\s")) {
1069: if (isModifier(mti)) {
1070: mods.append(mti);
1071: mods.append(" ");
1072: } else {
1073: type.append(mti);
1074: type.append(" ");
1075: }
1076: }
1077:
1078: return new String[] { mods.toString().trim(),
1079: type.toString().trim() };
1080: }
1081:
1082: }
|