You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

IgnoreList.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. /*
  2. * Copyright (c) 2006-2017 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.parser.common;
  23. import java.util.ArrayList;
  24. import java.util.List;
  25. import java.util.regex.PatternSyntaxException;
  26. import java.util.stream.Collectors;
  27. /**
  28. * Parser Ignore list.
  29. */
  30. public class IgnoreList {
  31. /** Arraylist storing ignore patterns. */
  32. protected final List<String> ignoreInfo = new ArrayList<>();
  33. /**
  34. * Creates a new instance of RegexStringList.
  35. */
  36. public IgnoreList() {
  37. // Do nothing
  38. }
  39. /**
  40. * Creates a new instance of RegexStringList, with the specified items.
  41. *
  42. * @param items Items to add to this RegexStringList
  43. */
  44. public IgnoreList(final Iterable<String> items) {
  45. addAll(items);
  46. }
  47. /**
  48. * Add a new ignore pattern to the ignore list.
  49. *
  50. * @param pattern Regex syntax for the ignore (Pattern is matched case-insensitively as ^pattern$)
  51. */
  52. public void add(final String pattern) {
  53. for (String target : ignoreInfo) {
  54. if (pattern.equalsIgnoreCase(target)) {
  55. return;
  56. }
  57. }
  58. ignoreInfo.add(pattern);
  59. }
  60. /**
  61. * Adds a set of patterns to the list.
  62. *
  63. * @param patterns A list of patterns to be added
  64. */
  65. public void addAll(final Iterable<String> patterns) {
  66. for (String pattern : patterns) {
  67. add(pattern);
  68. }
  69. }
  70. /**
  71. * Delete an ignore from the list.
  72. *
  73. * @param position Position in the list to remove
  74. */
  75. public void remove(final int position) {
  76. if (position < count()) {
  77. ignoreInfo.remove(position);
  78. }
  79. }
  80. /**
  81. * Clear the ignore list.
  82. */
  83. public void clear() {
  84. ignoreInfo.clear();
  85. }
  86. /**
  87. * Check if a string matches any of the ignores in the list.
  88. *
  89. * @param check String to check (Patterns are matched case-insensitively as ^pattern$)
  90. * @return integer showing the position of the first match in the ignore list (-1 if none)
  91. * @throws PatternSyntaxException if one of the items in the list is an invalid regex
  92. */
  93. public int matches(final String check) throws PatternSyntaxException {
  94. for (int i = 0; i < count(); i++) {
  95. if (check.matches("(?i)" + get(i))) {
  96. return i;
  97. }
  98. }
  99. return -1;
  100. }
  101. /**
  102. * Check if a string matches a specific ignore in the list.
  103. *
  104. * @param position Position to check
  105. * @param check String to check (Patterns are matched case-insensitively as ^pattern$)
  106. * @return boolean true/false
  107. * @throws PatternSyntaxException if the item is an invalid regex
  108. */
  109. public boolean matches(final int position, final String check) throws
  110. PatternSyntaxException {
  111. return position < count() && check.matches("(?i)" + get(position));
  112. }
  113. /**
  114. * Get the ignore pattern in a given position in the list.
  115. *
  116. * @param position Position to check
  117. * @return String showing the pattern. ("" if position isn't valid)
  118. */
  119. public String get(final int position) {
  120. if (position < count()) {
  121. return ignoreInfo.get(position);
  122. } else {
  123. return "";
  124. }
  125. }
  126. /**
  127. * Change the ignore pattern in a given position in the list.
  128. *
  129. * @param position Position to change
  130. * @param pattern New pattern
  131. */
  132. public void set(final int position, final String pattern) {
  133. if (position < count()) {
  134. ignoreInfo.set(position, pattern);
  135. }
  136. }
  137. /**
  138. * Get the amount of ignores in the list.
  139. *
  140. * @return int showing the number of ignores
  141. */
  142. public int count() {
  143. return ignoreInfo.size();
  144. }
  145. /**
  146. * Adds the specified simple pattern to this ignore list.
  147. *
  148. * @param pattern The simple pattern to be added
  149. */
  150. public void addSimple(final String pattern) {
  151. add(simpleToRegex(pattern));
  152. }
  153. /**
  154. * Determines if this list can be converted to a simple list.
  155. *
  156. * @return True if this list can be converted, false otherwise.
  157. */
  158. public boolean canConvert() {
  159. try {
  160. getSimpleList();
  161. return true;
  162. } catch (UnsupportedOperationException ex) {
  163. return false;
  164. }
  165. }
  166. /**
  167. * Retrieves a list of regular expressions in this ignore list.
  168. *
  169. * @return All expressions in this ignore list
  170. */
  171. public List<String> getRegexList() {
  172. return new ArrayList<>(ignoreInfo);
  173. }
  174. /**
  175. * Retrieves a list of simple expressions in this ignore list.
  176. *
  177. * @return All expressions in this ignore list, converted to simple expressions
  178. * @throws UnsupportedOperationException if an expression can't be converted
  179. */
  180. public List<String> getSimpleList() throws UnsupportedOperationException {
  181. return ignoreInfo.stream().map(IgnoreList::regexToSimple).collect(Collectors.toList());
  182. }
  183. /**
  184. * Converts a regular expression into a simple expression.
  185. *
  186. * @param regex The regular expression to be converted
  187. * @return A simple expression corresponding to the regex
  188. * @throws UnsupportedOperationException if the regex cannot be converted
  189. */
  190. protected static String regexToSimple(final String regex)
  191. throws UnsupportedOperationException {
  192. final StringBuilder res = new StringBuilder(regex.length());
  193. final ConversionState state = new ConversionState();
  194. for (char part : regex.toCharArray()) {
  195. if (state.getAndResetLastCharWasDot()) {
  196. handleCharFollowingDot(state, res, part);
  197. } else if (state.getAndResetEscaped()) {
  198. handleEscapedChar(res, part);
  199. } else {
  200. handleNormalChar(state, res, part);
  201. }
  202. }
  203. if (state.getAndResetEscaped()) {
  204. throw new UnsupportedOperationException("Cannot convert to "
  205. + "simple expression: trailing backslash");
  206. } else if (state.getAndResetLastCharWasDot()) {
  207. res.append('?');
  208. }
  209. return res.toString();
  210. }
  211. /**
  212. * Handles a single char that was preceded by a '.' when converting from a regex.
  213. *
  214. * @param state The current state of conversion.
  215. * @param builder The builder to append data to.
  216. * @param character The character in question.
  217. */
  218. private static void handleCharFollowingDot(final ConversionState state,
  219. final StringBuilder builder, final char character) {
  220. if (character == '*') {
  221. builder.append('*');
  222. } else {
  223. builder.append('?');
  224. handleNormalChar(state, builder, character);
  225. }
  226. }
  227. /**
  228. * Handles a single char that was escaped when converting from a regex.
  229. *
  230. * @param builder The builder to append data to.
  231. * @param character The character in question.
  232. */
  233. private static void handleEscapedChar(final StringBuilder builder, final char character) {
  234. if (character == '?' || character == '*') {
  235. throw new UnsupportedOperationException("Cannot convert to"
  236. + " simple expression: ? or * is escaped.");
  237. }
  238. builder.append(character);
  239. }
  240. /**
  241. * Handles a single normal character when converting from a regex.
  242. *
  243. * @param state The current state of conversion.
  244. * @param builder The builder to append data to.
  245. * @param character The character in question.
  246. */
  247. private static void handleNormalChar(final ConversionState state,
  248. final StringBuilder builder, final char character) {
  249. if (character == '\\') {
  250. state.setEscaped();
  251. } else if (character == '.') {
  252. state.setLastCharWasDot();
  253. } else if ("^$[](){}|+*?".indexOf(character) > -1) {
  254. throw new UnsupportedOperationException("Cannot convert to"
  255. + " simple expression: unescaped special char: " + character);
  256. } else {
  257. builder.append(character);
  258. }
  259. }
  260. /**
  261. * Converts a simple expression to a regular expression.
  262. *
  263. * @param regex The simple expression to be converted
  264. * @return A corresponding regular expression
  265. */
  266. protected static String simpleToRegex(final String regex) {
  267. final StringBuilder res = new StringBuilder(regex.length());
  268. for (char part : regex.toCharArray()) {
  269. switch (part) {
  270. case '.':
  271. case '^':
  272. case '$':
  273. case '[':
  274. case ']':
  275. case '\\':
  276. case '(':
  277. case ')':
  278. case '{':
  279. case '}':
  280. case '|':
  281. case '+':
  282. res.append('\\').append(part);
  283. break;
  284. case '?':
  285. res.append('.');
  286. break;
  287. case '*':
  288. res.append(".*");
  289. break;
  290. default:
  291. res.append(part);
  292. break;
  293. }
  294. }
  295. return res.toString();
  296. }
  297. /**
  298. * Utility class to represent state while converting a regex to a simple form.
  299. */
  300. private static final class ConversionState {
  301. private boolean escaped;
  302. private boolean lastCharWasDot;
  303. public boolean getAndResetLastCharWasDot() {
  304. final boolean oldValue = lastCharWasDot;
  305. lastCharWasDot = false;
  306. return oldValue;
  307. }
  308. public void setLastCharWasDot() {
  309. lastCharWasDot = true;
  310. }
  311. public boolean getAndResetEscaped() {
  312. final boolean oldValue = escaped;
  313. escaped = false;
  314. return oldValue;
  315. }
  316. public void setEscaped() {
  317. escaped = true;
  318. }
  319. }
  320. }