/plugins/Beauty/tags/beauty-0.7.0/src/beauty/beautifiers/DefaultBeautifier.java

# · Java · 884 lines · 585 code · 96 blank · 203 comment · 217 complexity · c61636f93882db968b4aad383f1f4edc MD5 · raw file

  1. package beauty.beautifiers;
  2. import java.io.*;
  3. import java.util.*;
  4. import java.util.regex.*;
  5. import org.gjt.sp.jedit.Buffer;
  6. import org.gjt.sp.jedit.jEdit;
  7. import org.gjt.sp.jedit.Mode;
  8. import org.gjt.sp.jedit.syntax.DefaultTokenHandler;
  9. import org.gjt.sp.jedit.syntax.Token;
  10. import beauty.BeautyPlugin;
  11. import beauty.PrivilegedAccessor;
  12. import static beauty.beautifiers.Constants.*;
  13. /**
  14. * This is a default beautifier to use when there is no specific beautifier
  15. * available. This can make for a pretty decent beautifier if configured
  16. * correctly.
  17. */
  18. public class DefaultBeautifier extends Beautifier {
  19. private String modeName = null;
  20. private boolean prePadOperator = false;
  21. private boolean prePadFunction = false;
  22. private boolean prePadDigit = false;
  23. private boolean prePadKeyword1 = false;
  24. private boolean prePadKeyword2 = false;
  25. private boolean prePadKeyword3 = false;
  26. private boolean prePadKeyword4 = false;
  27. private boolean postPadOperator = false;
  28. private boolean postPadFunction = false;
  29. private boolean postPadDigit = false;
  30. private boolean postPadKeyword1 = false;
  31. private boolean postPadKeyword2 = false;
  32. private boolean postPadKeyword3 = false;
  33. private boolean postPadKeyword4 = false;
  34. private boolean labelOnSeparateLine = true;
  35. private String prePadCharacters = "";
  36. private String postPadCharacters = "";
  37. private String dontPrePadCharacters = "";
  38. private String dontPostPadCharacters = "";
  39. // these are comma separated strings now, not a list of characters
  40. private String preInsertLineCharacters = "";
  41. private String postInsertLineCharacters = "";
  42. private boolean collapseBlankLines = false;
  43. private boolean collapseLinearWhitespace = false;
  44. private boolean indentLines = false;
  45. private String indentOpenBrackets = "";
  46. private String indentCloseBrackets = "";
  47. private String unalignedOpenBrackets = "";
  48. private String unalignedCloseBrackets = "";
  49. private String indentNextLine = "";
  50. private String unindentThisLine = "";
  51. private String electricKeys = "";
  52. // this constructor is used for testing
  53. public DefaultBeautifier() {
  54. }
  55. public DefaultBeautifier(String modeName) {
  56. if (modeName == null || modeName.isEmpty()) {
  57. throw new IllegalArgumentException("mode name was null");
  58. }
  59. this.modeName = modeName;
  60. Properties props = BeautyPlugin.getCustomModeProperties(modeName);
  61. prePadOperator = "true".equals(props.getProperty(PRE_PAD_OPERATORS)) ? true : false;
  62. prePadFunction = "true".equals(props.getProperty(PRE_PAD_FUNCTIONS)) ? true : false;
  63. prePadDigit = "true".equals(props.getProperty(PRE_PAD_DIGITS)) ? true : false;
  64. prePadKeyword1 = "true".equals(props.getProperty(PRE_PAD_KEYWORDS1)) ? true : false;
  65. prePadKeyword2 = "true".equals(props.getProperty(PRE_PAD_KEYWORDS2)) ? true : false;
  66. prePadKeyword3 = "true".equals(props.getProperty(PRE_PAD_KEYWORDS3)) ? true : false;
  67. prePadKeyword4 = "true".equals(props.getProperty(PRE_PAD_KEYWORDS4)) ? true : false;
  68. postPadOperator = "true".equals(props.getProperty(POST_PAD_OPERATORS)) ? true : false;
  69. postPadFunction = "true".equals(props.getProperty(POST_PAD_FUNCTIONS)) ? true : false;
  70. postPadDigit = "true".equals(props.getProperty(POST_PAD_DIGITS)) ? true : false;
  71. postPadKeyword1 = "true".equals(props.getProperty(POST_PAD_KEYWORDS1)) ? true : false;
  72. postPadKeyword2 = "true".equals(props.getProperty(POST_PAD_KEYWORDS2)) ? true : false;
  73. postPadKeyword3 = "true".equals(props.getProperty(POST_PAD_KEYWORDS3)) ? true : false;
  74. postPadKeyword4 = "true".equals(props.getProperty(POST_PAD_KEYWORDS4)) ? true : false;
  75. labelOnSeparateLine = "true".equals(props.getProperty(LABEL_ON_SEPARATE_LINE)) ? true : false;
  76. prePadCharacters = props.getProperty(PRE_PAD_CHARACTERS) == null ? "" : props.getProperty(PRE_PAD_CHARACTERS);
  77. postPadCharacters = props.getProperty(POST_PAD_CHARACTERS) == null ? "" : props.getProperty(POST_PAD_CHARACTERS);
  78. dontPrePadCharacters = props.getProperty(DONT_PRE_PAD_CHARACTERS) == null ? "" : props.getProperty(DONT_PRE_PAD_CHARACTERS);
  79. dontPostPadCharacters = props.getProperty(DONT_POST_PAD_CHARACTERS) == null ? "" : props.getProperty(DONT_POST_PAD_CHARACTERS);
  80. preInsertLineCharacters = props.getProperty(PRE_INSERT_LINE_CHARACTERS) == null ? "" : props.getProperty(PRE_INSERT_LINE_CHARACTERS);
  81. postInsertLineCharacters = props.getProperty(POST_INSERT_LINE_CHARACTERS) == null ? "" : props.getProperty(POST_INSERT_LINE_CHARACTERS);
  82. collapseBlankLines = "true".equals(props.getProperty(COLLAPSE_BLANK_LINES)) ? true : false;
  83. collapseLinearWhitespace = "true".equals(props.getProperty(COLLAPSE_LINEAR_WHITESPACE)) ? true : false;
  84. indentLines = "true".equals(props.getProperty(USE_JEDIT_INDENTER)) ? true : false;
  85. indentOpenBrackets = props.getProperty(INDENT_OPEN_BRACKETS) == null ? "" : props.getProperty(INDENT_OPEN_BRACKETS);
  86. indentCloseBrackets = props.getProperty(INDENT_CLOSE_BRACKETS) == null ? "" : props.getProperty(INDENT_CLOSE_BRACKETS);
  87. unalignedOpenBrackets = props.getProperty(UNALIGNED_OPEN_BRACKETS) == null ? "" : props.getProperty(UNALIGNED_OPEN_BRACKETS);
  88. unalignedCloseBrackets = props.getProperty(UNALIGNED_CLOSE_BRACKETS) == null ? "" : props.getProperty(UNALIGNED_CLOSE_BRACKETS);
  89. indentNextLine = props.getProperty(INDENT_NEXT_LINE) == null ? "" : props.getProperty(INDENT_NEXT_LINE);
  90. unindentThisLine = props.getProperty(UNINDENT_THIS_LINE) == null ? "" : props.getProperty(UNINDENT_THIS_LINE);
  91. electricKeys = props.getProperty(ELECTRIC_KEYS) == null ? "" : props.getProperty(ELECTRIC_KEYS);
  92. }
  93. /**
  94. * @param text The text to beautify.
  95. */
  96. public String beautify(String text) {
  97. StringBuilder sb = new StringBuilder(text);
  98. // Token padding can work on the raw text
  99. sb = padTokens(sb);
  100. // the next few operations need to work only on text, not on comments,
  101. // so split out the buffer contents into text and comments.
  102. List<PToken> tokens = parseTokens(sb);
  103. for (PToken token : tokens) {
  104. if (token.isText) {
  105. String s = token.tokenText;
  106. s = prePadCharacters(s);
  107. s = postPadCharacters(s);
  108. s = preInsertLineSeparators(s);
  109. s = postInsertLineSeparators(s);
  110. s = dontPrePadCharacters(s);
  111. s = dontPostPadCharacters(s);
  112. s = collapseBlankLines(s);
  113. s = collapseLinearWhitespace(s);
  114. token.tokenText = s;
  115. }
  116. }
  117. // convert the token list back to a string for padding keywords and indenting
  118. sb.setLength(0);
  119. for (PToken token : tokens) {
  120. sb.append(token.tokenText);
  121. }
  122. sb = padKeywords(sb);
  123. if (indentLines) {
  124. sb = indentLines(sb);
  125. }
  126. return sb.toString();
  127. }
  128. /**
  129. * Pad the tokens found by the jEdit syntax highlighting engine. In
  130. * general, I found that this is pretty horrible since many of the
  131. * mode files do a poor job of identifying tokens.
  132. */
  133. StringBuilder padTokens(StringBuilder sb) {
  134. if (prePadFunction || postPadFunction || prePadOperator || postPadOperator || prePadDigit || postPadDigit || prePadKeyword1 || postPadKeyword1 || prePadKeyword2 || postPadKeyword2 || prePadKeyword3 || postPadKeyword3 || prePadKeyword4 || postPadKeyword4) {
  135. try {
  136. File tempFile = File.createTempFile("tmp", null);
  137. tempFile.deleteOnExit();
  138. Buffer tempBuffer = jEdit.openTemporary(jEdit.getActiveView(), null, tempFile.getAbsolutePath(), true);
  139. tempBuffer.setMode(jEdit.getMode(modeName));
  140. tempBuffer.insert(0, sb.toString());
  141. sb.setLength(0);
  142. int firstLine = 0;
  143. int lastLine = tempBuffer.getLineCount();
  144. DefaultTokenHandler tokenHandler = new DefaultTokenHandler();
  145. for (int lineNum = firstLine; lineNum < lastLine; lineNum++) {
  146. tokenHandler.init();
  147. int lineStart = tempBuffer.getLineStartOffset(lineNum);
  148. tempBuffer.markTokens(lineNum, tokenHandler);
  149. Token token = tokenHandler.getTokens();
  150. int tokenStart = lineStart;
  151. String previousTokenText = "";
  152. byte previousTokenId = Token.NULL;
  153. String currentTokenText = tempBuffer.getText(tokenStart, token.length);
  154. String nextTokenText = token.next != null ? tempBuffer.getText(tokenStart + token.length, token.next.length) : "";
  155. while (token.id != Token.END) {
  156. // maybe pad start
  157. if (! previousTokenText.endsWith(" ")) { // NOPMD
  158. if ((token.id == Token.OPERATOR && prePadOperator && previousTokenId != Token.OPERATOR) || (token.id == Token.FUNCTION && prePadFunction) || (token.id == Token.DIGIT && prePadDigit) || (token.id == Token.KEYWORD1 && prePadKeyword1) || (token.id == Token.KEYWORD2 && prePadKeyword2) || (token.id == Token.KEYWORD3 && prePadKeyword3) || (token.id == Token.KEYWORD4 && prePadKeyword4)) { // NOPMD
  159. sb.append(' ');
  160. }
  161. }
  162. // maybe add a line for a label
  163. boolean onlyWhitespace = tempBuffer.getText(lineStart, tokenStart - lineStart).trim().length() > 0;
  164. if (token.id == Token.LABEL && labelOnSeparateLine && onlyWhitespace) {
  165. sb.append(getLineSeparator());
  166. }
  167. // definitely add text of current token
  168. sb.append(currentTokenText);
  169. // maybe pad after token
  170. if (! nextTokenText.startsWith(" ")) {
  171. if ((token.id == Token.OPERATOR && postPadOperator && token.next.id != Token.OPERATOR) || (token.id == Token.FUNCTION && postPadFunction) || (token.id == Token.DIGIT && postPadDigit) || (token.id == Token.KEYWORD1 && postPadKeyword1) || (token.id == Token.KEYWORD2 && postPadKeyword2) || (token.id == Token.KEYWORD3 && postPadKeyword3) || (token.id == Token.KEYWORD4 && postPadKeyword4)) { // NOPMD
  172. sb.append(' ');
  173. currentTokenText += " "; // NOPMD
  174. }
  175. }
  176. previousTokenText = currentTokenText;
  177. previousTokenId = token.id;
  178. currentTokenText = nextTokenText;
  179. tokenStart += token.length;
  180. token = token.next;
  181. if (token.next != null) {
  182. nextTokenText = tempBuffer.getText(tokenStart + token.length, token.next.length);
  183. }
  184. }
  185. if (lineNum <= lastLine - 2) {
  186. sb.append(getLineSeparator());
  187. }
  188. }
  189. if (sb.length() == 0) {
  190. sb = new StringBuilder(tempBuffer.getText(0, tempBuffer.getLength()));
  191. }
  192. } catch (IOException ioe) {
  193. ioe.printStackTrace();
  194. }
  195. }
  196. return sb;
  197. }
  198. /**
  199. * The user may specify a list of characters to pad in front of.
  200. */
  201. String prePadCharacters(String s) {
  202. if (prePadCharacters.length() == 0) {
  203. return s;
  204. }
  205. for (int i = 0; i < prePadCharacters.length(); i++) {
  206. char c = prePadCharacters.charAt(i);
  207. s = s.replaceAll("(\\S)[" + c + "]", "$1 " + c);
  208. }
  209. return s;
  210. }
  211. /**
  212. * The user may specify a list of characters to pad after.
  213. */
  214. String postPadCharacters(String s) {
  215. if (postPadCharacters.length() == 0) {
  216. return s;
  217. }
  218. for (int i = 0; i < postPadCharacters.length(); i++) {
  219. char c = postPadCharacters.charAt(i);
  220. s = s.replaceAll("[" + (c == '[' || c == ']' ? "\\" : "") + c + "](\\S)", c + " $1");
  221. }
  222. return s;
  223. }
  224. /**
  225. * The user may specify a comma separated list of strings before which a
  226. * line separator will be inserted. The strings are regular expressions.
  227. */
  228. String preInsertLineSeparators(String s) {
  229. if (preInsertLineCharacters.length() == 0) {
  230. return s;
  231. }
  232. s = trimStart(s);
  233. // need to deal with commas that may be part of a regex in a comma-
  234. // separated list of regex's. Find all escaped commas and replace with
  235. // c1f, then split on remaining commas, then revert the c1f's to normal
  236. // unescaped commas.
  237. String pilc = preInsertLineCharacters;
  238. pilc = pilc.replaceAll("\\\\,", "\\\\c1f");
  239. String[] chars = pilc.split(",");
  240. for (String c : chars) {
  241. c = c.replaceAll("\\\\c1f", ",");
  242. s = preInsertLineSeparators(s, c);
  243. }
  244. return s;
  245. }
  246. String preInsertLineSeparators(String s, String c) {
  247. try {
  248. String ls = getLineSeparator();
  249. String regex = "(?<!(" + getLSString() + "))(" + c + ")";
  250. Pattern p = Pattern.compile(regex);
  251. Matcher m = p.matcher(s);
  252. s = m.replaceAll(ls + "$2");
  253. } catch (Exception e) {
  254. e.printStackTrace();
  255. }
  256. return s;
  257. }
  258. /**
  259. * The user may specify a comma separated list of strings after which a
  260. * line separator will be inserted.
  261. */
  262. String postInsertLineSeparators(String s) {
  263. if (postInsertLineCharacters.length() == 0) {
  264. return s;
  265. }
  266. s = trimEnd(s);
  267. // need to deal with commas that may be part of a regex in a comma-
  268. // separated list of regex's.
  269. String pilc = postInsertLineCharacters;
  270. pilc = pilc.replaceAll("\\\\,", "\\\\c1f");
  271. String[] chars = pilc.split(",");
  272. for (String c : chars) {
  273. c = c.replaceAll("\\\\c1f", ",");
  274. s = postInsertLineSeparators(s, c);
  275. }
  276. return s;
  277. }
  278. String postInsertLineSeparators(String s, String c) {
  279. try {
  280. String ls = getLineSeparator();
  281. String regex = "(" + c + ")(?!(" + getLSString() + "))";
  282. Pattern p = Pattern.compile(regex);
  283. Matcher m = p.matcher(s);
  284. s = m.replaceAll("$1" + ls);
  285. } catch (Exception e) {
  286. e.printStackTrace();
  287. }
  288. return s;
  289. }
  290. /**
  291. * Remove a single whitespace character from before a character. Only
  292. * one whitespace character is removed. The intent here is that a
  293. * character may be mis-identified as an operator token in the mode file,
  294. * so it gets padded as an operator. The user can add this character to
  295. * the don't pad list and have that padding removed. An example is the
  296. * javascript mode, where ; is defined as an operator.
  297. */
  298. String dontPrePadCharacters(String s) {
  299. if (dontPrePadCharacters.length() == 0) {
  300. return s;
  301. }
  302. if (dontPrePadCharacters.length() > 0) {
  303. for (int i = 0; i < dontPrePadCharacters.length(); i++) {
  304. char c = dontPrePadCharacters.charAt(i);
  305. s = s.replaceAll("\\s+[" + (c == '[' || c == ']' || c == '\\' ? "\\" : "") + c + "]", String.valueOf(c));
  306. }
  307. }
  308. return s;
  309. }
  310. /**
  311. * Remove a single whitespace character from after a character. Only
  312. * one whitespace character is removed. The intent here is that a
  313. * character may be mis-identified as an operator token in the mode file,
  314. * so it gets padded as an operator. The user can add this character to
  315. * the don't pad list and have that padding removed. An example is the
  316. * javascript mode, where ; is defined as an operator.
  317. */
  318. String dontPostPadCharacters(String s) {
  319. if (dontPostPadCharacters.length() == 0) {
  320. return s;
  321. }
  322. if (dontPostPadCharacters.length() > 0) {
  323. for (int i = 0; i < dontPostPadCharacters.length(); i++) {
  324. char c = dontPostPadCharacters.charAt(i);
  325. s = s.replaceAll("[" + (c == '[' || c == ']' || c == '\\' ? "\\" : "") + c + "]\\s+", String.valueOf(c));
  326. }
  327. }
  328. return s;
  329. }
  330. /**
  331. * Keywords get padded last rather than with padTokens. Otherwise, the other
  332. * pad/dontPad methods may eliminate the keyword padding, for example,
  333. * the space between "for (" may be removed by some other rule even when
  334. * pad keywords is checked.
  335. */
  336. StringBuilder padKeywords(StringBuilder sb) {
  337. if (prePadKeyword1 || postPadKeyword1 || prePadKeyword2 || postPadKeyword2 || prePadKeyword3 || postPadKeyword3 || prePadKeyword4 || postPadKeyword4) {
  338. try {
  339. File tempFile = File.createTempFile("tmp", null);
  340. tempFile.deleteOnExit();
  341. Buffer tempBuffer = jEdit.openTemporary(jEdit.getActiveView(), null, tempFile.getAbsolutePath(), true);
  342. tempBuffer.setMode(jEdit.getMode(modeName));
  343. tempBuffer.insert(0, sb.toString());
  344. sb.setLength(0);
  345. int firstLine = 0;
  346. int lastLine = tempBuffer.getLineCount();
  347. DefaultTokenHandler tokenHandler = new DefaultTokenHandler();
  348. for (int lineNum = firstLine; lineNum < lastLine; lineNum++) {
  349. tokenHandler.init();
  350. int lineStart = tempBuffer.getLineStartOffset(lineNum);
  351. tempBuffer.markTokens(lineNum, tokenHandler);
  352. Token token = tokenHandler.getTokens();
  353. int tokenStart = lineStart;
  354. String previousTokenText = "";
  355. String currentTokenText = tempBuffer.getText(tokenStart, token.length);
  356. String nextTokenText = token.next != null ? tempBuffer.getText(tokenStart + token.length, token.next.length) : "";
  357. while (token.id != Token.END) {
  358. // maybe pad start
  359. if (! previousTokenText.endsWith(" ")) { // NOPMD
  360. if ((token.id == Token.KEYWORD1 && prePadKeyword1) || (token.id == Token.KEYWORD2 && prePadKeyword2) || (token.id == Token.KEYWORD3 && prePadKeyword3) || (token.id == Token.KEYWORD4 && prePadKeyword4)) { // NOPMD
  361. sb.append(' ');
  362. }
  363. }
  364. // definitely add text of current token, but remove any stray whitespace
  365. sb.append(currentTokenText);
  366. // maybe pad after token
  367. if (! nextTokenText.startsWith(" ")) {
  368. if ((token.id == Token.KEYWORD1 && postPadKeyword1) || (token.id == Token.KEYWORD2 && postPadKeyword2) || (token.id == Token.KEYWORD3 && postPadKeyword3) || (token.id == Token.KEYWORD4 && postPadKeyword4)) { // NOPMD
  369. sb.append(' ');
  370. currentTokenText += " "; // NOPMD
  371. }
  372. }
  373. previousTokenText = currentTokenText;
  374. currentTokenText = nextTokenText;
  375. tokenStart += token.length;
  376. token = token.next;
  377. if (token.next != null) {
  378. nextTokenText = tempBuffer.getText(tokenStart + token.length, token.next.length);
  379. }
  380. }
  381. if (lineNum <= lastLine - 2) {
  382. sb.append(getLineSeparator());
  383. }
  384. }
  385. if (sb.length() == 0) {
  386. sb = new StringBuilder(tempBuffer.getText(0, tempBuffer.getLength()));
  387. }
  388. } catch (IOException ioe) {
  389. ioe.printStackTrace();
  390. }
  391. }
  392. return sb;
  393. }
  394. /**
  395. * Collapse two or more blank lines to a single blank line.
  396. */
  397. String collapseBlankLines(String s) {
  398. if (! collapseBlankLines) {
  399. return s;
  400. }
  401. String regex = "(([ ]|[\\t])*(" + getLSString() + ")){2,}";
  402. s = s.replaceAll(regex, getLineSeparator());
  403. return s;
  404. }
  405. /**
  406. * Collapse multiple spaces and/or tabs to a single space.
  407. */
  408. String collapseLinearWhitespace(String s) {
  409. if (!collapseLinearWhitespace) {
  410. return s;
  411. }
  412. String regex = "([ ]|[\\t]){2,}";
  413. s = s.replaceAll(regex, " ");
  414. return s;
  415. }
  416. /**
  417. * Use the jEdit indenter to indent the lines.
  418. */
  419. StringBuilder indentLines(StringBuilder sb) {
  420. try {
  421. // unfortunate hack here -- the Mode class only loads the indenting rules once,
  422. // so need to set the rules to null so they get reloaded with the user defined
  423. // properties for this custom beautifier.
  424. Mode mode = jEdit.getMode(modeName);
  425. PrivilegedAccessor.setValue(mode, "indentRules", null);
  426. // now the indenting rules can be set and will be used by jEdit
  427. mode.setProperty(INDENT_OPEN_BRACKETS, indentOpenBrackets);
  428. mode.setProperty(INDENT_CLOSE_BRACKETS, indentCloseBrackets);
  429. mode.setProperty(UNALIGNED_OPEN_BRACKETS, unalignedOpenBrackets);
  430. mode.setProperty(UNALIGNED_CLOSE_BRACKETS, unalignedCloseBrackets);
  431. mode.setProperty(INDENT_NEXT_LINE, indentNextLine);
  432. mode.setProperty(UNINDENT_THIS_LINE, unindentThisLine);
  433. mode.setProperty(ELECTRIC_KEYS, electricKeys);
  434. mode.getIndentRules(); // causes the indent rules to be reloaded.
  435. File tempFile = File.createTempFile("tmp", null);
  436. tempFile.deleteOnExit();
  437. Buffer tempBuffer = jEdit.openTemporary(jEdit.getActiveView(), null, tempFile.getAbsolutePath(), true);
  438. tempBuffer.setMode(mode);
  439. tempBuffer.insert(0, sb.toString());
  440. tempBuffer.indentLines(0, tempBuffer.getLineCount() - 1);
  441. sb = new StringBuilder(tempBuffer.getText(0, tempBuffer.getLength()));
  442. if (initialLevel > 0) {
  443. // do additional indenting, for example, this is the case when
  444. // javascript is inside a <script> block in an html or jsp file
  445. // and the entire block needs indented some more to fit with
  446. // the rest of the file.
  447. StringBuilder pad = new StringBuilder();
  448. for (int i = 0; i < initialLevel; i++) {
  449. pad.append(indent);
  450. }
  451. String all = sb.toString();
  452. String ls = getLineSeparator();
  453. String[] lines = all.split(ls);
  454. sb.setLength(0);
  455. for (String line : lines) {
  456. sb.append(pad).append(line).append(ls);
  457. }
  458. }
  459. } catch (Exception ioe) {
  460. ioe.printStackTrace();
  461. }
  462. return sb;
  463. }
  464. /**
  465. * @return A string representing the line separator escaped for using it
  466. * in a regular expression.
  467. */
  468. private String getLSString() {
  469. String ls = getLineSeparator();
  470. if ("\r".equals(ls)) {
  471. return "\\r";
  472. }
  473. if ("\r\n".equals(ls)) {
  474. return "\\r\\n";
  475. }
  476. return "\\n";
  477. }
  478. /**
  479. * This splits the input string into a list of tokens, where a token is either
  480. * some modifiable text, a comment, or a literal. This allows the pad/don't pad and pre/post insert
  481. * methods to work only on the modifiable portions of the input and leave the comments and literals alone.
  482. * This uses the jEdit syntax highlighting engine to do the parsing.
  483. * @param sb Some text to parse.
  484. * @param A list of PTokens. Modifiable PTokens have isText set to true.
  485. */
  486. public List<PToken> parseTokens(StringBuilder sb) {
  487. List<PToken> ptokens = new ArrayList<PToken>();
  488. try {
  489. StringBuilder textBuffer = new StringBuilder();
  490. StringBuilder commentBuffer = new StringBuilder();
  491. StringBuilder literalBuffer = new StringBuilder();
  492. File tempFile = File.createTempFile("tmp", null);
  493. tempFile.deleteOnExit();
  494. Buffer tempBuffer = jEdit.openTemporary(jEdit.getActiveView(), null, tempFile.getAbsolutePath(), true);
  495. tempBuffer.setMode(jEdit.getMode(modeName));
  496. tempBuffer.insert(0, sb.toString());
  497. int firstLine = 0;
  498. int lastLine = tempBuffer.getLineCount();
  499. DefaultTokenHandler tokenHandler = new DefaultTokenHandler();
  500. for (int lineNum = firstLine; lineNum < lastLine; lineNum++) {
  501. tokenHandler.init();
  502. int lineStart = tempBuffer.getLineStartOffset(lineNum);
  503. tempBuffer.markTokens(lineNum, tokenHandler);
  504. Token token = tokenHandler.getTokens();
  505. int tokenStart = lineStart;
  506. String currentTokenText = tempBuffer.getText(tokenStart, token.length);
  507. String nextTokenText = token.next != null ? tempBuffer.getText(tokenStart + token.length, token.next.length) : "";
  508. while (token.id != Token.END) {
  509. if (token.id == Token.COMMENT1 || token.id == Token.COMMENT2 || token.id == Token.COMMENT3 || token.id == Token.COMMENT4) {
  510. // hit a comment token and there is text in the textBuffer, so
  511. // create a ptoken for the text
  512. if (textBuffer.length() > 0) {
  513. PToken textToken = new PToken();
  514. textToken.isText = true;
  515. textToken.tokenText = textBuffer.toString();
  516. ptokens.add(textToken);
  517. textBuffer.setLength(0);
  518. }
  519. // hit a comment token and there is text in the literalBuffer, so
  520. // create a ptoken for the literal
  521. if (literalBuffer.length() > 0) {
  522. PToken literalToken = new PToken();
  523. literalToken.isText = false;
  524. literalToken.tokenText = literalBuffer.toString();
  525. ptokens.add(literalToken);
  526. literalBuffer.setLength(0);
  527. }
  528. commentBuffer.append(currentTokenText);
  529. } else if (token.id == Token.LITERAL1 || token.id == Token.LITERAL2 || token.id == Token.LITERAL3 || token.id == Token.LITERAL4) {
  530. // hit a literal token and there is text in the textBuffer, so
  531. // create a ptoken for the text
  532. if (textBuffer.length() > 0) {
  533. PToken textToken = new PToken();
  534. textToken.isText = true;
  535. textToken.tokenText = textBuffer.toString();
  536. ptokens.add(textToken);
  537. textBuffer.setLength(0);
  538. }
  539. // hit a literal token and there is text in the literalBuffer, so
  540. // create a ptoken for the literal
  541. if (commentBuffer.length() > 0) {
  542. PToken commentToken = new PToken();
  543. commentToken.isText = false;
  544. commentToken.tokenText = commentBuffer.toString();
  545. ptokens.add(commentToken);
  546. commentBuffer.setLength(0);
  547. }
  548. literalBuffer.append(currentTokenText);
  549. } else {
  550. // in a regular, modifiable piece of code, close out the
  551. // comment and literal buffers
  552. if (commentBuffer.length() > 0) {
  553. PToken commentToken = new PToken();
  554. commentToken.isText = false;
  555. commentToken.tokenText = commentBuffer.toString();
  556. ptokens.add(commentToken);
  557. commentBuffer.setLength(0);
  558. }
  559. if (literalBuffer.length() > 0) {
  560. PToken literalToken = new PToken();
  561. literalToken.isText = false;
  562. literalToken.tokenText = literalBuffer.toString();
  563. ptokens.add(literalToken);
  564. literalBuffer.setLength(0);
  565. }
  566. textBuffer.append(currentTokenText);
  567. }
  568. currentTokenText = nextTokenText;
  569. tokenStart += token.length;
  570. token = token.next;
  571. if (token.next != null) {
  572. nextTokenText = tempBuffer.getText(tokenStart + token.length, token.next.length);
  573. }
  574. }
  575. if (lineNum <= lastLine - 2 && textBuffer.length() > 0) {
  576. textBuffer.append(getLineSeparator());
  577. }
  578. if (lineNum <= lastLine - 2 && commentBuffer.length() > 0) {
  579. commentBuffer.append(getLineSeparator());
  580. }
  581. }
  582. if (textBuffer.length() > 0) {
  583. PToken textToken = new PToken();
  584. textToken.isText = true;
  585. textToken.tokenText = textBuffer.toString();
  586. ptokens.add(textToken);
  587. }
  588. if (commentBuffer.length() > 0) {
  589. PToken commentToken = new PToken();
  590. commentToken.isText = false;
  591. commentToken.tokenText = commentBuffer.toString();
  592. ptokens.add(commentToken);
  593. }
  594. if (literalBuffer.length() > 0) {
  595. PToken literalToken = new PToken();
  596. literalToken.isText = false;
  597. literalToken.tokenText = literalBuffer.toString();
  598. ptokens.add(literalToken);
  599. }
  600. } catch (IOException ioe) {
  601. ioe.printStackTrace();
  602. }
  603. return ptokens;
  604. }
  605. // A class to tell modifiable text from comments and literals.
  606. public class PToken {
  607. boolean isText = false;
  608. String tokenText;
  609. }
  610. String trimStart(String s) {
  611. StringBuilder sb = new StringBuilder(s);
  612. while(sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) {
  613. sb.deleteCharAt(0);
  614. }
  615. return sb.toString();
  616. }
  617. String trimEnd(String s) {
  618. StringBuilder sb = new StringBuilder(s);
  619. while(sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) {
  620. sb.deleteCharAt(sb.length() - 1);
  621. }
  622. return sb.toString();
  623. }
  624. ////////////////////////////////////////////////////////////////////////////////
  625. ////////////////////////////////////////////////////////////////////////////////
  626. /// everything below is to support unit tests.
  627. /**
  628. * Sets the value of prePadOperator.
  629. * @param prePadOperator The value to assign prePadOperator.
  630. */
  631. public void setPrePadOperator(boolean prePadOperator) {
  632. this.prePadOperator = prePadOperator;
  633. }
  634. /**
  635. * Sets the value of prePadFunction.
  636. * @param prePadFunction The value to assign prePadFunction.
  637. */
  638. public void setPrePadFunction(boolean prePadFunction) {
  639. this.prePadFunction = prePadFunction;
  640. }
  641. /**
  642. * Sets the value of prePadDigit.
  643. * @param prePadDigit The value to assign prePadDigit.
  644. */
  645. public void setPrePadDigit(boolean prePadDigit) {
  646. this.prePadDigit = prePadDigit;
  647. }
  648. /**
  649. * Sets the value of prePadKeyword1.
  650. * @param prePadKeyword1 The value to assign prePadKeyword1.
  651. */
  652. public void setPrePadKeyword1(boolean prePadKeyword1) {
  653. this.prePadKeyword1 = prePadKeyword1;
  654. }
  655. /**
  656. * Sets the value of prePadKeyword2.
  657. * @param prePadKeyword2 The value to assign prePadKeyword2.
  658. */
  659. public void setPrePadKeyword2(boolean prePadKeyword2) {
  660. this.prePadKeyword2 = prePadKeyword2;
  661. }
  662. /**
  663. * Sets the value of prePadKeyword3.
  664. * @param prePadKeyword3 The value to assign prePadKeyword3.
  665. */
  666. public void setPrePadKeyword3(boolean prePadKeyword3) {
  667. this.prePadKeyword3 = prePadKeyword3;
  668. }
  669. /**
  670. * Sets the value of prePadKeyword4.
  671. * @param prePadKeyword4 The value to assign prePadKeyword4.
  672. */
  673. public void setPrePadKeyword4(boolean prePadKeyword4) {
  674. this.prePadKeyword4 = prePadKeyword4;
  675. }
  676. /**
  677. * Sets the value of postPadOperator.
  678. * @param postPadOperator The value to assign postPadOperator.
  679. */
  680. public void setPostPadOperator(boolean postPadOperator) {
  681. this.postPadOperator = postPadOperator;
  682. }
  683. /**
  684. * Sets the value of postPadFunction.
  685. * @param postPadFunction The value to assign postPadFunction.
  686. */
  687. public void setPostPadFunction(boolean postPadFunction) {
  688. this.postPadFunction = postPadFunction;
  689. }
  690. /**
  691. * Sets the value of postPadDigit.
  692. * @param postPadDigit The value to assign postPadDigit.
  693. */
  694. public void setPostPadDigit(boolean postPadDigit) {
  695. this.postPadDigit = postPadDigit;
  696. }
  697. /**
  698. * Sets the value of postPadKeyword1.
  699. * @param postPadKeyword1 The value to assign postPadKeyword1.
  700. */
  701. public void setPostPadKeyword1(boolean postPadKeyword1) {
  702. this.postPadKeyword1 = postPadKeyword1;
  703. }
  704. /**
  705. * Sets the value of postPadKeyword2.
  706. * @param postPadKeyword2 The value to assign postPadKeyword2.
  707. */
  708. public void setPostPadKeyword2(boolean postPadKeyword2) {
  709. this.postPadKeyword2 = postPadKeyword2;
  710. }
  711. /**
  712. * Sets the value of postPadKeyword3.
  713. * @param postPadKeyword3 The value to assign postPadKeyword3.
  714. */
  715. public void setPostPadKeyword3(boolean postPadKeyword3) {
  716. this.postPadKeyword3 = postPadKeyword3;
  717. }
  718. /**
  719. * Sets the value of postPadKeyword4.
  720. * @param postPadKeyword4 The value to assign postPadKeyword4.
  721. */
  722. public void setPostPadKeyword4(boolean postPadKeyword4) {
  723. this.postPadKeyword4 = postPadKeyword4;
  724. }
  725. /**
  726. * Sets the value of labelOnSeparateLine.
  727. * @param labelOnSeparateLine The value to assign labelOnSeparateLine.
  728. */
  729. public void setLabelOnSeparateLine(boolean labelOnSeparateLine) {
  730. this.labelOnSeparateLine = labelOnSeparateLine;
  731. }
  732. /**
  733. * Sets the value of prePadCharacters.
  734. * @param prePadCharacters The value to assign prePadCharacters.
  735. */
  736. public void setPrePadCharacters(String prePadCharacters) {
  737. this.prePadCharacters = prePadCharacters;
  738. }
  739. /**
  740. * Sets the value of postPadCharacters.
  741. * @param postPadCharacters The value to assign postPadCharacters.
  742. */
  743. public void setPostPadCharacters(String postPadCharacters) {
  744. this.postPadCharacters = postPadCharacters;
  745. }
  746. /**
  747. * Sets the value of dontPrePadCharacters.
  748. * @param dontPrePadCharacters The value to assign dontPrePadCharacters.
  749. */
  750. public void setDontPrePadCharacters(String dontPrePadCharacters) {
  751. this.dontPrePadCharacters = dontPrePadCharacters;
  752. }
  753. /**
  754. * Sets the value of dontPostPadCharacters.
  755. * @param dontPostPadCharacters The value to assign dontPostPadCharacters.
  756. */
  757. public void setDontPostPadCharacters(String dontPostPadCharacters) {
  758. this.dontPostPadCharacters = dontPostPadCharacters;
  759. }
  760. /**
  761. * Sets the value of preInsertLineCharacters.
  762. * @param preInsertLineCharacters The value to assign preInsertLineCharacters.
  763. */
  764. public void setPreInsertLineCharacters(String preInsertLineCharacters) {
  765. this.preInsertLineCharacters = preInsertLineCharacters;
  766. }
  767. /**
  768. * Sets the value of postInsertLineCharacters.
  769. * @param postInsertLineCharacters The value to assign postInsertLineCharacters.
  770. */
  771. public void setPostInsertLineCharacters(String postInsertLineCharacters) {
  772. this.postInsertLineCharacters = postInsertLineCharacters;
  773. }
  774. /**
  775. * Sets the value of collapseBlankLines.
  776. * @param collapseBlankLines The value to assign collapseBlankLines.
  777. */
  778. public void setCollapseBlankLines(boolean collapseBlankLines) {
  779. this.collapseBlankLines = collapseBlankLines;
  780. }
  781. /**
  782. * Sets the value of collapseLinearWhitespace.
  783. * @param collapseLinearWhitespace The value to assign collapseLinearWhitespace.
  784. */
  785. public void setCollapseLinearWhitespaces(boolean collapseLinearWhitespace) {
  786. this.collapseLinearWhitespace = collapseLinearWhitespace;
  787. }
  788. }